hermoso 0.1.414 → 0.1.417
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 +20 -16
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -4336,8 +4336,10 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
4336
4336
|
// MULTI-SHOT + its per-shot prompt cap (2026-10-05): the cap is a vendor rule the schema hides, so it is said here.
|
|
4337
4337
|
const _shotLine = (d.options?.video?.models || []).filter(m => m.multiShot).map(m => `${m.id}${m.shotPromptMaxChars ? ` (each shot prompt at most ${m.shotPromptMaxChars} characters; longer is refused, never cut)` : ''}`).join(', ');
|
|
4338
4338
|
const _rm = d.remake; // the remake engines (recast_motion / restyle_video / edit_video engine), priced live
|
|
4339
|
-
const _rmLine = _rm ? `\nRemake engines (recast_motion, restyle_video, edit_video engine): auto picks, for a clip with no real-looking person, ${Object.entries(_rm.defaults || {}).map(([k, v]) => `${k} → ${v}`).join(', ')}; with a person in the clip, ${Object.entries(_rm.faceDefaults || {}).map(([k, v]) => `${k} → ${v}`).join(', ')}${_rm.personClipLibraryDefaults ? ` (a person clip our face library can carry, i.e. one of our renders or faceRoute on a paid plan: ${Object.entries(_rm.personClipLibraryDefaults).map(([k, v]) => `${k} → ${v}`).join(', ')})` : ''}. seedance ${(_rm.seconds || []).join('-')}s, up to ${_rm.imagesMax} pictures, ${Object.entries(_rm.seedanceCredits || {}).map(([res, m]) => `${res} ${Object.entries(m).map(([s, c]) => `${s}s ${c}cr`).join(' / ')}`).join('; ')}${_rm.wanCredits ? `. wan clips up to ${_rm.wanMaxSourceSeconds}s, ${Object.entries(_rm.wanCredits).map(([res, m]) => `${res} ${Object.entries(m).map(([s, c]) => `${s}s ${c}cr`).join(' / ')}`).join('; ')}` : ''}` : '';
|
|
4340
|
-
const
|
|
4339
|
+
const _rmLine = _rm ? `\nRemake engines (recast_motion, restyle_video, edit_video engine): auto picks, for a clip with no real-looking person, ${Object.entries(_rm.defaults || {}).map(([k, v]) => `${k} → ${v}`).join(', ')}; with a person in the clip, ${Object.entries(_rm.faceDefaults || {}).map(([k, v]) => `${k} → ${v}`).join(', ')}${_rm.personClipLibraryDefaults ? ` (a person clip our face library can carry, i.e. one of our renders or faceRoute on a paid plan: ${Object.entries(_rm.personClipLibraryDefaults).map(([k, v]) => `${k} → ${v}`).join(', ')})` : ''}. seedance ${(_rm.seconds || []).join('-')}s, up to ${_rm.imagesMax} pictures, ${Object.entries(_rm.seedanceCredits || {}).map(([res, m]) => `${res} ${Object.entries(m).map(([s, c]) => `${s}s ${c}cr`).join(' / ')}`).join('; ')}${_rm.wanCredits ? `. wan clips up to ${_rm.wanMaxSourceSeconds}s, ${Object.entries(_rm.wanCredits).map(([res, m]) => `${res} ${Object.entries(m).map(([s, c]) => `${s}s ${c}cr`).join(' / ')}`).join('; ')}${_rm.wanLabel ? ` (${_rm.wanLabel})` : ''}` : ''}${_rm.wanPrimeCredits ? `. wan-prime (Wan 3.0 Prime by name) ${Object.entries(_rm.wanPrimeCredits).map(([res, m]) => `${res} ${Object.entries(m).map(([s, c]) => `${s}s ${c}cr`).join(' / ')}`).join('; ')}` : ''}` : '';
|
|
4340
|
+
const _je = d.jobEngines; // the certified engine per job (watched side-by-side tests, 2026-10-19)
|
|
4341
|
+
const _jeLine = _je && _je.defaults ? `\nRecommended engine per job (watched side-by-side tests): ${Object.entries(_je.defaults).map(([k, v]) => `${k} → ${v}`).join(', ')}; a talking ad is one take of up to ${_je.talkingMaxSeconds}s. Pricier picks that looked no better for that job: ${Object.entries(_je.notNoticeablyBetter || {}).map(([k, v]) => `${k}: ${v.join(', ')}`).join('; ')}. If the user picks one, you may say once that the recommended one looks the same and costs less; never switch it.` : '';
|
|
4342
|
+
const text = `Image: ${d.image ? img : 'unavailable'}\nVideo: ${d.video ? vid : 'unavailable'}\n${_lenLine}${_extLine ? `\nExtend a clip (generate_video extend:true + refVideo): ${_extLine}` : ''}${_shotLine ? `\nMulti-shot clips (generate_video shots): ${_shotLine}` : ''}${_rsLine ? `\nRestyle looks (restyle_video style): ${_rsLine}` : ''}${_rmLine}${_jeLine}\n${_pickLine}\n${_resLine}${_motionLine}${_avLine}\nVoice engines (generate_voice): ${voice}\nWriting models (generate_text): ${llm}\ncanEdit:${d.canEdit} canAvatar:${d.canAvatar}\nRecipes (${(d.recipes || []).length}): ${(d.recipes || []).slice(0, 20).map(r => r.id).join(', ')}…\n\n${CAPABILITY_MAP}`;
|
|
4341
4343
|
return ok(text + connLine, d);
|
|
4342
4344
|
}));
|
|
4343
4345
|
|
|
@@ -17794,7 +17796,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17794
17796
|
// so an import lands in the same list the app renders and syncs across devices.
|
|
17795
17797
|
server.registerTool('import_from_cloud', {
|
|
17796
17798
|
title: 'Import a Drive / OneDrive folder into the Library',
|
|
17797
|
-
description: "Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public, so this is the only way in) and stores a durable Hermoso url for each. Give `folderId` from list_drive_files / list_onedrive_files with onlyFolders — omit it for the root. GOOGLE DRIVE ONLY SHOWS WHAT THE USER HANDED OVER: our Drive scope is `drive.file`, so Hermoso can see the files and folders it created plus the ones the user picked with the Google picker in the app, and NEVER their whole Drive
|
|
17799
|
+
description: "Pull the files in a Google Drive or OneDrive FOLDER into this profile's Library, so they can be used like anything rendered here — published, scheduled, cloned, used as a product photo or a reference. Hermoso downloads each file with the user's own connected account (a Drive/OneDrive file is not public, so this is the only way in) and stores a durable Hermoso url for each. Give `folderId` from list_drive_files / list_onedrive_files with onlyFolders — omit it for the root. GOOGLE DRIVE ONLY SHOWS WHAT THE USER HANDED OVER: our Drive scope is `drive.file`, so Hermoso can see the files and folders it created plus the ones the user picked with the Google picker in the app, and NEVER their whole Drive. Picking a FOLDER hands over the folder alone, not the files in it, so if a folder comes back empty the reply carries a link that opens the picker inside it, where the user selects every file at once (Cmd+A / Ctrl+A); give them that link. OneDrive has no such limit. SUBFOLDERS ARE NOT WALKED and Google-native docs (Docs/Sheets/Slides) have no file to download: both are reported back BY NAME rather than silently dropped, along with anything too large or unreadable, so you can tell the user exactly what did and did not come across.",
|
|
17798
17800
|
inputSchema: {
|
|
17799
17801
|
provider: z.enum(['drive', 'onedrive']).describe('which cloud — `drive` is Google Drive, `onedrive` is Microsoft OneDrive'),
|
|
17800
17802
|
folderId: z.string().optional().describe('the folder to import, from list_drive_files / list_onedrive_files (onlyFolders:true). Omit for the root of the drive.'),
|
|
@@ -17803,6 +17805,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17803
17805
|
outputSchema: {
|
|
17804
17806
|
imported: z.array(z.object({ name: z.string().optional(), url: z.string().optional(), kind: z.string().optional(), bytes: z.number().optional() })).optional(),
|
|
17805
17807
|
skipped: z.array(z.object({ name: z.string().optional(), why: z.string().optional() })).optional(),
|
|
17808
|
+
note: z.string().optional(),
|
|
17806
17809
|
},
|
|
17807
17810
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
17808
17811
|
}, wrap(async (a) => {
|
|
@@ -17819,7 +17822,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17819
17822
|
.map((x) => ({ url: x.url, kind: x.kind || '', model: a.provider === 'drive' ? 'Imported from Google Drive' : 'Imported from OneDrive', name: x.name || '', at: Date.now() }));
|
|
17820
17823
|
if (rows.length) await writeStore('heist.assets.v1', [...rows, ...list].slice(0, 300));
|
|
17821
17824
|
}
|
|
17822
|
-
if (!imported.length && !skipped.length) return ok('That folder is empty — nothing to import.', { imported: [], skipped: [] });
|
|
17825
|
+
if (!imported.length && !skipped.length) return ok(a.provider === 'drive' && d.note ? d.note : 'That folder is empty — nothing to import.', { imported: [], skipped: [], ...(d.note ? { note: d.note } : {}) }); // an empty DRIVE folder is usually the drive.file scope: the server's note says why and links the fix
|
|
17823
17826
|
const lines = [
|
|
17824
17827
|
imported.length ? `Imported ${imported.length} file${imported.length === 1 ? '' : 's'} into the Library:` : 'Nothing was imported.',
|
|
17825
17828
|
...imported.map((x) => ` • ${x.name || x.url} → ${x.url}`),
|
|
@@ -17830,7 +17833,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17830
17833
|
|
|
17831
17834
|
server.registerTool('list_drive_files', {
|
|
17832
17835
|
title: 'List Google Drive files',
|
|
17833
|
-
description: 'List the Google Drive files & folders Hermoso can reach — the ones it created, plus any the user handed over with the Google file picker in the app (the drive.file scope exposes nothing else, never their entire Drive). This is how you find the id of a file the user picked. Filter by query (name contains …), folderId (contents of a folder), or onlyFolders:true. Paginate with pageToken. Read-only.',
|
|
17836
|
+
description: 'List the Google Drive files & folders Hermoso can reach — the ones it created, plus any the user handed over with the Google file picker in the app (the drive.file scope exposes nothing else, never their entire Drive). This is how you find the id of a file the user picked. Filter by query (name contains …), folderId (contents of a folder), or onlyFolders:true. Paginate with pageToken. Read-only. A FOLDER: picking a folder hands over the folder alone, not its files, so a folder can list empty or partly; the reply then carries `pickUrl`, an app link that opens the Google Drive picker inside that folder where the user selects every file at once (Cmd+A / Ctrl+A). Give the user that link, then list again.',
|
|
17834
17837
|
inputSchema: {
|
|
17835
17838
|
query: z.string().optional().describe('only files whose name contains this'),
|
|
17836
17839
|
folderId: z.string().optional().describe('list the contents of this folder id'),
|
|
@@ -17839,12 +17842,13 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17839
17842
|
pageToken: z.string().optional().describe('cursor from a previous call'),
|
|
17840
17843
|
includeTrashed: z.boolean().optional().describe('include trashed files (default false)'),
|
|
17841
17844
|
},
|
|
17842
|
-
outputSchema: { files: z.array(z.any()).optional(), cursor: z.string().nullable().optional() },
|
|
17845
|
+
outputSchema: { files: z.array(z.any()).optional(), cursor: z.string().nullable().optional(), note: z.string().optional(), pickUrl: z.string().optional(), folderVisible: z.boolean().nullable().optional() },
|
|
17843
17846
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
17844
17847
|
}, wrap(async (a) => {
|
|
17845
17848
|
const d = await apiGet('/api/drive/files', a);
|
|
17846
17849
|
const lines = (d.files || []).map(f => `• ${f.name} (${f.id})${f.mimeType && f.mimeType.includes('folder') ? ' [folder]' : ''}${f.webViewLink ? ` — ${f.webViewLink}` : ''}`);
|
|
17847
|
-
|
|
17850
|
+
// A folder listing carries the server's note (why it is empty or partial, and the link that fixes it): print it, never a bare "(none)".
|
|
17851
|
+
return ok(`${(d.files || []).length} item(s):\n${lines.join('\n') || '(none)'}${d.note ? `\n${d.note}${d.pickUrl && !String(d.note).includes(d.pickUrl) ? ` Link: ${d.pickUrl}` : ''}` : ''}`, d);
|
|
17848
17852
|
}));
|
|
17849
17853
|
server.registerTool('get_drive_file', {
|
|
17850
17854
|
title: 'Get a Drive file’s details',
|
|
@@ -18587,11 +18591,11 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
18587
18591
|
server.group('create');
|
|
18588
18592
|
server.registerTool('generate_voice', {
|
|
18589
18593
|
title: 'Generate voiceover',
|
|
18590
|
-
description: "RAW text-to-speech from the voice-model catalog: speak a script in a chosen voice and return the served MP3 URL. For a standalone voiceover / narration clip — NOT for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). engine picks the voice model (default 'seed-audio'; also 'eleven-v3', 'minimax-speech', 'kokoro'); voice is a preset name from that engine (see hermoso_capabilities -> voice engines) — a name that engine does not have is REFUSED for free with its real list, and a few engines generate their own voice and take no preset at all (the reply says which voice actually spoke). Paid (a couple of credits by length; ≤900 characters).",
|
|
18594
|
+
description: "RAW text-to-speech from the voice-model catalog: speak a script in a chosen voice and return the served MP3 URL. For a standalone voiceover / narration clip — NOT for adding audio to a video (render_ad and generate_video voice their own spots; change_voice re-voices a finished clip). engine picks the voice model (default 'seed-audio'; also 'eleven-v4' (Eleven v4; the retired id 'eleven-v3' still answers, on v4), 'minimax-speech', 'kokoro'); voice is a preset name from that engine (see hermoso_capabilities -> voice engines) — a name that engine does not have is REFUSED for free with its real list, and a few engines generate their own voice and take no preset at all (the reply says which voice actually spoke). Paid (a couple of credits by length; ≤900 characters).",
|
|
18591
18595
|
inputSchema: {
|
|
18592
18596
|
text: z.string().describe('the script to speak (≤900 characters)'),
|
|
18593
|
-
engine: z.string().optional().describe("voice-engine id: 'seed-audio' (default), 'eleven-
|
|
18594
|
-
voice: z.string().optional().describe("a voice preset from the chosen engine (e.g. 'Aria'/'George' on eleven-
|
|
18597
|
+
engine: z.string().optional().describe("voice-engine id: 'seed-audio' (default), 'eleven-v4' (Eleven v4), 'minimax-speech', or 'kokoro' — listed in hermoso_capabilities"),
|
|
18598
|
+
voice: z.string().optional().describe("a voice preset from the chosen engine (e.g. 'Aria'/'George' on eleven-v4, 'stokie_en' on seed-audio) — omit for the engine default"),
|
|
18595
18599
|
},
|
|
18596
18600
|
outputSchema: {
|
|
18597
18601
|
audio: z.string().optional().describe('the served absolute URL of the MP3 voice clip'),
|
|
@@ -18650,7 +18654,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
18650
18654
|
creative: z.object({}).passthrough().describe('the FULL structured output of plan_ad (must contain video_storyboard)'),
|
|
18651
18655
|
creator: z.string().optional().describe('CAST A SAVED CREATOR in this ad — their id from list_creators, or the name you know them by (“Sarah”), or a PRESET AI creator from list_creators presets by exact name or id (free, no generation). Their saved portrait becomes the on-camera identity for the whole spot, so the same face carries across every act and across every ad you render for this brand — and because we already have their picture, the character portrait this pipeline would otherwise generate is skipped, so casting somebody costs LESS than not casting them. Omit to let the ad cast a fresh person — EXCEPT for a CREATOR account (onboarded from their own @handle): their own saved likeness is cast by default when the plan has a person on camera and the account is on a paid Hermoso plan (a free account gets a fresh AI person), and the read-back says `default:true`; pass "none" to render without them. Refused for free, with nothing rendered, if the name matches nobody or more than one creator, if an explicitly named creator is cast on a plan with nobody on camera, or if a REAL person is cast on a free plan. ' + LIKENESS_TERMS),
|
|
18652
18656
|
faceRoute: z.enum(['face_lane']).optional().describe(FACE_ROUTE_DESC),
|
|
18653
|
-
model: z.string().optional().describe('video model id from hermoso_capabilities (default: the plan
|
|
18657
|
+
model: z.string().optional().describe('video model id from hermoso_capabilities (default: the recommended engine for the plan: wan-3 for a person talking to camera in one take up to 15s, seedance-2.5 otherwise). Naming one is a DELIBERATE pick — the server asks before ever swapping it (no silent fallback); a pricier pick the side-by-side tests found no better comes back with a one-line GUIDANCE note you may pass on once'),
|
|
18654
18658
|
durationSeconds: z.number().optional().describe('total ad length in seconds (supported range 4–180; outside that it is clamped). Omit to honor the plan’s own duration — that is almost always right. This only RE-TIMES an already-authored board (its scenes are scaled to fit), it does NOT re-write it, so to change the length of the ad the user asked for, re-run plan_ad with durationSeconds instead. A length that fits ONE clip of the render model renders as one continuous pass; longer is stitched from acts filled to that model’s clip maximum with the remainder last — the maximum is 15s on most models and 30s on the longest-clip one, so use dryRun:true to see the exact act split for free before spending.'),
|
|
18655
18659
|
aspectRatio: z.string().optional().describe('output aspect ratio, e.g. 9:16 (default) / 1:1 / 16:9'),
|
|
18656
18660
|
resolution: z.enum(['480p', '720p', '1080p', '4k']).optional().describe("'1080p' default (what we ship and bill for); '480p'/'720p' = cheaper draft passes, '4k' = premium final delivery (more credits). NOT EVERY MODEL OFFERS EVERY TIER — this enum is what the tool accepts, and each model's OWN `resolutions` list in hermoso_capabilities is what it can actually render (the longest-clip 30s model, for one, tops out at 720p). Ask for a tier the chosen model does not list and it is rendered at that model's best available tier instead, with nothing in the reply saying so — so check `resolutions` before promising anyone 1080p or 4k."),
|
|
@@ -19139,7 +19143,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
19139
19143
|
interactionId: z.string().optional().describe('with extend:true on an Omni model: the interactionId returned by an earlier render on that model — continues it from its own stored context instead of re-uploading refVideo.'),
|
|
19140
19144
|
durationSeconds: z.number().optional().describe('length of THIS ONE clip in seconds — pick one of the CHOSEN model’s listed durations from hermoso_capabilities (never a generic guess: the lists differ per model, from 4–8s on the short models up to 30s on the longest-clip one). This is a single continuous generation, so it CANNOT exceed that model’s longest clip: a longer ask is REFUSED with nothing rendered and nothing charged (it is never quietly truncated). Past ~15s only the long-clip models qualify, and an unnamed render is routed by the narrower auto-pool — so NAME the model in `model` when you are asking for a long single take. For a spot longer than any one clip, use plan_ad with durationSeconds then render_ad, which stitches acts of at most one model clip each (on a 15s-clip model, 40s = 15+15+10).'),
|
|
19141
19145
|
aspectRatio: z.string().optional().describe("default '9:16'"),
|
|
19142
|
-
model: z.string().optional().describe('video model id from hermoso_capabilities; a named model is never swapped without asking. Omit to let the router pick'),
|
|
19146
|
+
model: z.string().optional().describe('video model id from hermoso_capabilities; a named model is never swapped without asking. Omit to let the router pick. RECOMMENDED per job (jobEngines in hermoso_capabilities, from watched side-by-side tests): wan-3 for a person talking to camera (one take up to 15s), seedance-2.5 for product, lifestyle and 30s single takes. If the user picks a pricier model the tests found no better for that job (e.g. wan-3-prime or seedance-2.5 for a talking clip), you may say once that the recommended one looks the same and costs less; never switch it'),
|
|
19143
19147
|
faceRoute: z.enum(['face_lane']).optional().describe(FACE_ROUTE_DESC),
|
|
19144
19148
|
resolution: z.enum(['480p', '720p', '1080p', '4k']).optional().describe("'1080p' default (what we ship and bill for); '480p'/'720p' = cheaper draft passes, '4k' = premium final delivery (more credits). NOT EVERY MODEL OFFERS EVERY TIER — this enum is what the tool accepts, and each model's OWN `resolutions` list in hermoso_capabilities is what it can actually render. Ask for a tier the chosen model does not list and it is rendered at that model's best available tier instead, with nothing in the reply saying so — so check `resolutions` before promising anyone 1080p or 4k."),
|
|
19145
19149
|
cameraMove: z.enum(['orbit', 'orbit_left', 'orbit_half', 'orbit_full', 'rise', 'crane_up', 'push_in', 'pull_back', 'reveal']).optional().describe('A named camera move around the still in refImage, spelled exactly as the enum gives it: an orbit (a quarter turn, the default), orbiting left, a half turn or a full turntable, a rise, a crane up, a push in, a pull back, or a reveal. Only the camera-controls model renders one (minimax-h3-max-camera, listed with its moves in hermoso_capabilities): pass it with model omitted and that model is picked, or with that model named; any other named model is refused by name with nothing charged. Needs refImage.'),
|
|
@@ -21428,14 +21432,14 @@ function memoryNoteVerdict(text) {
|
|
|
21428
21432
|
// described look) runs as a video edit: same engines, same price, same free preservation read-back as edit_video.
|
|
21429
21433
|
server.registerTool('restyle_video', {
|
|
21430
21434
|
title: 'Restyle a video',
|
|
21431
|
-
description: "RESTYLE a clip into a new LOOK: every frame redrawn, shots, motion, camera, framing, timing and sound kept. Looks: `Restyle looks` in hermoso_capabilities (claymation, knitted yarn, cel-shaded CG anime…), your own words, or a look BUILT FROM YOUR IMAGES (styleImages); characters draws your own characters in. engine auto (default) picks by what is in the clip: a real-looking person -> kling (Kling O3 Edit, 3-15s); none -> wan (Wan 3.0
|
|
21435
|
+
description: "RESTYLE a clip into a new LOOK: every frame redrawn, shots, motion, camera, framing, timing and sound kept. Looks: `Restyle looks` in hermoso_capabilities (claymation, knitted yarn, cel-shaded CG anime…), your own words, or a look BUILT FROM YOUR IMAGES (styleImages); characters draws your own characters in. engine auto (default) picks by what is in the clip: a real-looking person -> kling (Kling O3 Edit, 3-15s); none -> wan (Wan 3.0, recommended, ≤15s) else seedance (Seedance 2.5, 4-30s); wan-prime = Wan 3.0 Prime by name (looked the same side by side, costs more: say so once if the user picks it). Paid; dryRun quotes it. One change only: edit_video.",
|
|
21432
21436
|
inputSchema: {
|
|
21433
21437
|
video: z.string().describe('the source video URL (a render, job result or list_library)'),
|
|
21434
21438
|
style: z.string().optional().describe('a look id from hermoso_capabilities (e.g. claymation, knitted-yarn, cel-shaded-anime-cg)'),
|
|
21435
21439
|
describe: z.string().optional().describe('a look in the user’s own words, when no preset fits, or extra detail on top of a preset'),
|
|
21436
21440
|
styleImages: z.array(z.string()).max(9).optional().describe('pictures that ARE the look (their style, never their subject)'),
|
|
21437
21441
|
characters: z.array(z.string()).max(4).optional().describe('characters to draw the people as. ' + LIKENESS_TERMS),
|
|
21438
|
-
engine: z.enum(['auto', 'seedance', 'wan', 'kling']).optional().describe('default auto'),
|
|
21442
|
+
engine: z.enum(['auto', 'seedance', 'wan', 'wan-prime', 'kling']).optional().describe('default auto'),
|
|
21439
21443
|
resolution: z.enum(['480p', '720p', '1080p']).optional().describe('Seedance and Wan; default 1080p'),
|
|
21440
21444
|
faceRoute: z.enum(['face_lane']).optional().describe(FACE_ROUTE_CLIP_DESC),
|
|
21441
21445
|
},
|
|
@@ -21605,13 +21609,13 @@ function memoryNoteVerdict(text) {
|
|
|
21605
21609
|
|
|
21606
21610
|
server.registerTool('recast_motion', {
|
|
21607
21611
|
title: 'Recast motion',
|
|
21608
|
-
description: "Motion transfer: re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; sound kept. image (+ images, @Image1, @Image2… in prompt) = who and what. engine auto (default), a person clip recast with people: one photo (talking, dance or full-body) or an AI person made from words, ≤15s -> wan (on the beat, keeps set and framing, follows every blink and mouth shape); several photos (one per person) or >15s -> h3 (H3 Max Recast: ≤4 photos, one per person, 5-30s, no shot over 15s, keeps set, light, cuts; engine h3 picks it by name); otherwise seedance (Seedance 2.5, ≤9 pictures, 4-30s, keeps set and beats; a person clip only if our render or faceRoute, else wan/kling). kling = Kling Motion Control (ONE picture, its background becomes the set, 3-30s); wan = Wan 3.0
|
|
21612
|
+
description: "Motion transfer: re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; sound kept. image (+ images, @Image1, @Image2… in prompt) = who and what. engine auto (default), a person clip recast with people: one photo (talking, dance or full-body) or an AI person made from words, ≤15s -> wan (on the beat, keeps set and framing, follows every blink and mouth shape); several photos (one per person) or >15s -> h3 (H3 Max Recast: ≤4 photos, one per person, 5-30s, no shot over 15s, keeps set, light, cuts; engine h3 picks it by name); otherwise seedance (Seedance 2.5, ≤9 pictures, 4-30s, keeps set and beats; a person clip only if our render or faceRoute, else wan/kling). kling = Kling Motion Control (ONE picture, its background becomes the set, 3-30s); wan = Wan 3.0, recommended (≤9 pictures, keeps the set, ≤15s); wan-prime = Wan 3.0 Prime by name (looked the same side by side, costs more: say so once if the user picks it). voice/language: the new performer gets a new voice (same timing) or speaks another language (lip-synced), in the same job and price. Billed per output second, ~5 min per 5s; dryRun quotes.",
|
|
21609
21613
|
inputSchema: {
|
|
21610
21614
|
image: z.string().optional().describe('who performs it: the actor/character image URL (@Image1). Required on kling. No one yet: generate_image a portrait of an AI person from words (no reference photo) and pass its url, or a saved / preset creator’s portrait (list_creators). ' + LIKENESS_TERMS),
|
|
21611
21615
|
images: z.array(z.string()).max(8).optional().describe('seedance/wan: more pictures, @Image2…: a character, product, outfit or place'),
|
|
21612
21616
|
video: z.string().describe('the reference video whose motion to re-perform'),
|
|
21613
21617
|
prompt: z.string().optional().describe("optional, e.g. 'same moves, new location: Tokyo at night'"),
|
|
21614
|
-
engine: z.enum(['auto', 'h3', 'seedance', 'wan', 'kling']).optional().describe('default auto'),
|
|
21618
|
+
engine: z.enum(['auto', 'h3', 'seedance', 'wan', 'wan-prime', 'kling']).optional().describe('default auto'),
|
|
21615
21619
|
resolution: z.enum(['480p', '720p', '1080p']).optional().describe('seedance and wan default 1080p; h3 takes 720p or 1080p, default the clip’s own'),
|
|
21616
21620
|
orientation: z.enum(['video', 'image']).optional().describe("kling only: which aspect to keep: the video's (default) or the image's"),
|
|
21617
21621
|
faceRoute: z.enum(['face_lane']).optional().describe(FACE_ROUTE_CLIP_DESC),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.417",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 912 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, Telegram and Google Business Profile. 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",
|