hermoso 0.1.358 → 0.1.359
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 -6
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -20244,19 +20244,21 @@ function memoryNoteVerdict(text) {
|
|
|
20244
20244
|
// the engine; this wrapper only fills the brand's product photo. Held out of the default list on size (ON_DEMAND_TOOLS).
|
|
20245
20245
|
server.registerTool('recast_hook', {
|
|
20246
20246
|
title: 'Recast a viral hook with your subject',
|
|
20247
|
-
description: "RECAST A VIRAL HOOK
|
|
20247
|
+
description: "RECAST A VIRAL HOOK with the brand's own subject (the egg rolling off the table becomes your product; your host does the viral move), then join it into your clip. hook: a public post link (TikTok, Reel, Facebook, X, YouTube; one lookup) or a video file. Subject: creator (a saved creator's name or id), image (a product or a person; a real person's photo records their consent), or neither for the brand's first product photo. A DUO OR GROUP (2-4 people): subjects [{creator or image, position: 'left'|'right'|'center' or your own words, refs?, outfit?}], each bound to its place in the frame, keeping that performer's movement and timing on the original sound. LOCATION AND OUTFITS, same pass: location (a place photo or words) moves the scene; outfit (a photo or words) re-dresses the subject, subjects[].outfit each person; with no subject named nobody is swapped and only those change (useBrand:true also swaps in the brand product). Pictures are references, 4 in all (people or product, then location, then outfits); past 4 it is refused by name: put one in words. FULL LENGTH: past the editor's one pass (15 s with a face) the window is recast in passes, each continuing from the previous one's frame and crossfaded across the cut, under the original sound: one video up to 60 s, quoted as passes plus join; the reply names the moment to watch. mode 'auto' (default): 'swap' is a region edit that keeps camera, set, light, timing, on-screen text and sound; 'motion' makes your person perform the hook's moves (needs a person in the hook; not with location or outfit). swap: your own words for what changes. start/end pick the window (3 s minimum; default: a group or a location/outfit change takes the whole clip, one subject the first 10 s). PAID, QUOTED FIRST: without confirm it returns the plan and credits; call again with confirm:true once the user okays it. Then get_job until done, and post_edit with videoUrl = the result and ops [{op:'join', clips:[{url: your clip}], bridge:{kind:'impact'}}].",
|
|
20248
20248
|
inputSchema: {
|
|
20249
20249
|
hook: z.string().describe('the viral hook: a post link or a video URL'),
|
|
20250
20250
|
creator: z.string().optional().describe("a saved creator's name or id (list_creators)"),
|
|
20251
20251
|
image: z.string().optional().describe('a picture of the subject instead: a product or a person'),
|
|
20252
|
-
subjects: z.array(z.object({ creator: z.string().optional().describe("a saved creator's name or id"), image: z.string().optional().describe('or a picture of this person'), position: z.string().optional().describe("where they are in the hook: 'left', 'right', 'center', or your own words ('who raps first'); all or none"), refs: z.array(z.string()).optional().describe("up to 3 more views of the SAME person (a saved creator's pose plates ride automatically)"), name: z.string().optional() }).passthrough()).max(4).optional().describe('a duo or group recast, 2-4 people, instead of creator/image'),
|
|
20252
|
+
subjects: z.array(z.object({ creator: z.string().optional().describe("a saved creator's name or id"), image: z.string().optional().describe('or a picture of this person'), position: z.string().optional().describe("where they are in the hook: 'left', 'right', 'center', or your own words ('who raps first'); all or none"), refs: z.array(z.string()).optional().describe("up to 3 more views of the SAME person (a saved creator's pose plates ride automatically)"), name: z.string().optional(), outfit: z.string().optional().describe('what this person wears instead: an outfit photo URL or words; alone (no creator/image) it keeps this person and changes only the clothes') }).passthrough()).max(4).optional().describe('a duo or group recast, 2-4 people, instead of creator/image'),
|
|
20253
|
+
location: z.string().optional().describe('move the scene: a photo URL of the place, or words ("a sunny beach at golden hour")'),
|
|
20254
|
+
outfit: z.string().optional().describe('what the subject (or, with no subject, the main person in the hook) wears instead: an outfit photo URL or words'),
|
|
20253
20255
|
swap: z.string().optional().describe('what changes, in your own words'),
|
|
20254
20256
|
mode: z.enum(['auto', 'swap', 'motion']).optional(),
|
|
20255
20257
|
start: z.number().optional(), end: z.number().optional(),
|
|
20256
20258
|
prompt: z.string().optional().describe('extra direction for the render'),
|
|
20257
20259
|
model: z.string().optional().describe('swap: a clip editor id (hermoso_capabilities); omit for the default'),
|
|
20258
20260
|
tier: z.enum(['pro', 'standard']).optional().describe('motion: the motion-transfer tier (default pro)'),
|
|
20259
|
-
useBrand: z.boolean().optional().describe('false = no brand product photo'),
|
|
20261
|
+
useBrand: z.boolean().optional().describe('false = no brand product photo; true with location/outfit and no subject = also swap in the brand product'),
|
|
20260
20262
|
confirm: z.boolean().optional().describe('true = render it (after the user okayed the quote)'),
|
|
20261
20263
|
},
|
|
20262
20264
|
outputSchema: { plan: z.any().optional(), job: z.any().optional(), credits: z.number().optional(), queued: z.boolean().optional() },
|
|
@@ -20264,12 +20266,13 @@ function memoryNoteVerdict(text) {
|
|
|
20264
20266
|
}, wrap(async (a) => {
|
|
20265
20267
|
let b = {};
|
|
20266
20268
|
const subs = Array.isArray(a.subjects) ? a.subjects.filter(Boolean) : [];
|
|
20267
|
-
|
|
20269
|
+
const sceneOnly = !a.creator && !a.image && !subs.some((x) => x && (x.creator || x.image)) && (a.location || a.outfit || subs.some((x) => x && x.outfit)); // a location / outfit change with nobody swapped
|
|
20270
|
+
if (a.useBrand !== false && !a.creator && !a.image && !subs.length && (!sceneOnly || a.useBrand === true)) { try { b = (await readStore('heist.brand.v1')) || {}; } catch { b = {}; } }
|
|
20268
20271
|
// a person's photo as the subject is recorded as implied likeness consent SERVER-side, only when the free face check finds a face there (a product photo is not a likeness)
|
|
20269
|
-
const body = { hook: a.hook, ...(a.creator ? { creator: a.creator } : {}), ...(a.image ? { image: a.image } : {}), ...(subs.length ? { subjects: subs } : {}), ...(a.swap ? { swap: a.swap } : {}), ...(a.mode && a.mode !== 'auto' ? { mode: a.mode } : {}), ...(a.start != null ? { start: a.start } : {}), ...(a.end != null ? { end: a.end } : {}), ...(a.prompt ? { prompt: a.prompt } : {}), ...(a.model ? { model: a.model } : {}), ...(a.tier ? { tier: a.tier } : {}), ...(b && Array.isArray(b.productImages) && b.productImages.length ? { brand: { name: b.name || '', product: b.sells || b.name || '', productImages: b.productImages.slice(0, 1) } } : {}), ...(a.confirm === true ? { confirm: true } : {}) };
|
|
20272
|
+
const body = { hook: a.hook, ...(a.creator ? { creator: a.creator } : {}), ...(a.image ? { image: a.image } : {}), ...(subs.length ? { subjects: subs } : {}), ...(a.location ? { location: a.location } : {}), ...(a.outfit ? { outfit: a.outfit } : {}), ...(sceneOnly && a.useBrand === true ? { useBrand: true } : {}), ...(a.swap ? { swap: a.swap } : {}), ...(a.mode && a.mode !== 'auto' ? { mode: a.mode } : {}), ...(a.start != null ? { start: a.start } : {}), ...(a.end != null ? { end: a.end } : {}), ...(a.prompt ? { prompt: a.prompt } : {}), ...(a.model ? { model: a.model } : {}), ...(a.tier ? { tier: a.tier } : {}), ...(b && Array.isArray(b.productImages) && b.productImages.length ? { brand: { name: b.name || '', product: b.sells || b.name || '', productImages: b.productImages.slice(0, 1) } } : {}), ...(a.confirm === true ? { confirm: true } : {}) };
|
|
20270
20273
|
const r = await apiPost('/api/hooks/recast', body);
|
|
20271
20274
|
const p = r?.data || r;
|
|
20272
|
-
const head = `${p.mode === 'motion' ? 'Motion transfer' : 'Swap'} on ${p.engine}: ${p.hook?.window?.seconds}s of the hook (${p.hook?.window?.start}-${p.hook?.window?.end}s of ${p.hook?.durationSeconds}s). ${Array.isArray(p.subjects) ? `Subjects: ${p.subjects.map((x) => `${x.element} ${x.position}${x.name ? ` "${x.name}"` : ''} (${x.kind}${x.refs ? `, ${x.refs} more view${x.refs === 1 ? '' : 's'}` : ''})`).join('; ')}` : `Subject: ${p.subject?.kind}${p.subject?.name ? ` "${p.subject.name}"` : ''}`}. Face in the hook: ${p.hook?.face}; in the subject: ${p.subject?.face}. Why: ${p.why}.${Array.isArray(p.passes) ? `\nPasses: ${p.passes.map((w) => `${w.start}-${w.end}s${w.credits != null ? ` (${w.credits} credits)` : ''}`).join(', ')}; join${p.join?.credits != null ? ` (${p.join.credits} credits)` : ''}: ${p.join?.how || 'a hard cut'}.` : ''}${p.windowNote ? `\n${p.windowNote}` : ''}${p.instruction ? `\nInstruction: ${p.instruction}` : ''}${p.consent ? `\n${p.consent}` : ''}`;
|
|
20275
|
+
const head = `${p.mode === 'motion' ? 'Motion transfer' : 'Swap'} on ${p.engine}: ${p.hook?.window?.seconds}s of the hook (${p.hook?.window?.start}-${p.hook?.window?.end}s of ${p.hook?.durationSeconds}s). ${Array.isArray(p.subjects) ? `Subjects: ${p.subjects.map((x) => `${x.element} ${x.position}${x.name ? ` "${x.name}"` : ''} (${x.kind}${x.refs ? `, ${x.refs} more view${x.refs === 1 ? '' : 's'}` : ''})`).join('; ')}` : (p.subject?.kind === 'kept' ? 'Nobody is swapped' : `Subject: ${p.subject?.kind}${p.subject?.name ? ` "${p.subject.name}"` : ''}`)}${p.location ? `; location: ${p.location.kind === 'photo' ? p.location.image : p.location.text}` : ''}${p.outfit ? `; outfit: ${p.outfit.kind === 'photo' ? p.outfit.image : p.outfit.text}` : ''}${Array.isArray(p.outfits) ? `; outfits: ${p.outfits.map((o) => `${o.who}: ${o.kind === 'photo' ? o.image : o.text}`).join('; ')}` : ''}. Face in the hook: ${p.hook?.face}; in the subject: ${p.subject?.face}. Why: ${p.why}.${Array.isArray(p.passes) ? `\nPasses: ${p.passes.map((w) => `${w.start}-${w.end}s${w.credits != null ? ` (${w.credits} credits)` : ''}`).join(', ')}; join${p.join?.credits != null ? ` (${p.join.credits} credits)` : ''}: ${p.join?.how || 'a hard cut'}.` : ''}${p.windowNote ? `\n${p.windowNote}` : ''}${p.instruction ? `\nInstruction: ${p.instruction}` : ''}${p.consent ? `\n${p.consent}` : ''}`;
|
|
20273
20276
|
if (!p.queued) return { content: [{ type: 'text', text: `QUOTE (nothing rendered, nothing charged): about ${p.credits} credits.\n${head}\nTell the user the price; after they okay it, call recast_hook again with the same inputs and confirm:true.` }], structuredContent: { plan: p, credits: p.credits, queued: false } };
|
|
20274
20277
|
return { content: [{ type: 'text', text: `Recast queued as job ${p.job?.id} (about ${p.credits} credits).\n${head}\nCall get_job until it reports done; it has no file until then. ${p.next || ''}` }], structuredContent: { plan: p, job: p.job, credits: p.credits, queued: true } };
|
|
20275
20278
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.359",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "Marketing on autopilot, run from your own AI agent. 864 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 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",
|