hermoso 0.1.8 → 0.1.9
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/bin/hermoso.mjs +1 -1
- package/mcp/tools.mjs +55 -0
- package/package.json +2 -2
package/bin/hermoso.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// in skills/ wrap these commands. Same /api as the MCP server.
|
|
5
5
|
//
|
|
6
6
|
// npm i -g (from this repo) OR node bin/hermoso.mjs <cmd>
|
|
7
|
-
// hermoso auth login
|
|
7
|
+
// hermoso auth login # browser sign-in (loopback, like gh/heroku); --token <key> for CI
|
|
8
8
|
// hermoso capabilities # learn valid model ids + costs (run first)
|
|
9
9
|
// hermoso create --brand Flourish --product "protein pancakes" --format image
|
|
10
10
|
// hermoso generate image --prompt "…" --ref ./bag.png --wait
|
package/mcp/tools.mjs
CHANGED
|
@@ -662,6 +662,38 @@ export function registerTools(server) {
|
|
|
662
662
|
return okVideo(`Finished video ready: ${r.url} [job ${r.jobId}]`, r);
|
|
663
663
|
}));
|
|
664
664
|
|
|
665
|
+
|
|
666
|
+
server.registerTool('post_edit', {
|
|
667
|
+
title: 'Post-production edit',
|
|
668
|
+
description: "MECHANICAL post-production on an EXISTING rendered video (its served mp4 URL) — an ordered plan of whitelisted primitives executed by ffmpeg (+ Chrome for typeset cards) in seconds for ~2 credits flat, NO AI model, the original untouched (returns a NEW video). The lane for: append a branded end card ('add an end card with our logo and website' — ADDS its seconds, never re-renders), trim, speed (0.5-2x), mute (whole or a window), audio_gain (-20..+6 dB), fade_out, corner logo watermark, anti-AI film grain. Up to 6 ops per plan, applied in order. Brand assets (name/domain/logo/accent) load from the workspace brand automatically; override per-call if needed. NEVER use generate_video/render_ad for these mechanical asks.",
|
|
669
|
+
inputSchema: {
|
|
670
|
+
videoUrl: z.string().describe('the served URL of the video to edit'),
|
|
671
|
+
ops: z.array(z.object({
|
|
672
|
+
op: z.enum(['trim', 'speed', 'mute', 'audio_gain', 'fade_out', 'append_card', 'watermark', 'grain']),
|
|
673
|
+
start: z.number().optional().describe('trim/mute window start (s)'),
|
|
674
|
+
end: z.number().optional().describe('trim/mute window end (s)'),
|
|
675
|
+
factor: z.number().optional().describe('speed 0.5-2'),
|
|
676
|
+
db: z.number().optional().describe('audio_gain -20..+6 dB'),
|
|
677
|
+
seconds: z.number().optional().describe('fade_out 0.3-3s / append_card 2-5s'),
|
|
678
|
+
headline: z.string().optional().describe('append_card: line instead of the brand name'),
|
|
679
|
+
sub: z.string().optional().describe('append_card: small line under it (defaults to the brand website)'),
|
|
680
|
+
corner: z.enum(['tl', 'tr', 'bl', 'br']).optional().describe('watermark corner (default br)'),
|
|
681
|
+
intensity: z.enum(['default', 'strong']).optional().describe('grain look'),
|
|
682
|
+
})).describe('the ordered edit plan (max 6 ops)'),
|
|
683
|
+
brandName: z.string().optional().describe('override the workspace brand name'),
|
|
684
|
+
domain: z.string().optional().describe('override the brand website'),
|
|
685
|
+
accent: z.string().optional().describe('override the brand accent hex'),
|
|
686
|
+
},
|
|
687
|
+
outputSchema: { ...JOB_OUT },
|
|
688
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
689
|
+
}, wrap(async (a) => {
|
|
690
|
+
let b = {};
|
|
691
|
+
try { const bk = PROFILE !== 'default' ? `heist.brand.v1.${PROFILE}` : 'heist.brand.v1'; b = JSON.parse((await apiGet(`/api/store/${encodeURIComponent(bk)}`))?.value || 'null') || {}; } catch {}
|
|
692
|
+
const pal = (Array.isArray(b.palette) ? b.palette : []).filter(c => /^#[0-9a-f]{6}$/i.test(String(c || '')));
|
|
693
|
+
const r = await renderJob('postedit', { videoUrl: a.videoUrl, ops: (a.ops || []).slice(0, 6), brandName: a.brandName || b.name || '', domain: a.domain || b.domain || '', logo: b.logo || '', accent: a.accent || pal[0] || '' }, 'MCP post edit');
|
|
694
|
+
return okVideo(`Edited video ready: ${r.url}${Array.isArray(r?.raw?.applied) ? ` (${r.raw.applied.join(', ')})` : ''} [job ${r.jobId}]`, r);
|
|
695
|
+
}));
|
|
696
|
+
|
|
665
697
|
server.registerTool('fix_beat', {
|
|
666
698
|
title: 'Fix a video beat',
|
|
667
699
|
description: "Surgically re-render ONE time window (1.5-8s) of an existing rendered video and splice it back on the VIDEO TRACK ONLY — the rest of the video and ALL audio stay byte-identical. Use when one beat/shot is broken ('the shot at 8 seconds glitches') and a full re-render would waste the parts that worked; bills only the replacement clip's seconds (~1/3 of a full render). Do NOT pick a window covering spoken dialogue (a video-only splice under speech breaks lip-sync) — pass speechWindows to enforce this.",
|
|
@@ -1187,6 +1219,29 @@ export function registerTools(server) {
|
|
|
1187
1219
|
}));
|
|
1188
1220
|
|
|
1189
1221
|
// ---------- assets ----------
|
|
1222
|
+
|
|
1223
|
+
server.registerTool('list_library', {
|
|
1224
|
+
title: 'List library',
|
|
1225
|
+
description: "Browse this workspace's Library — every image/video generated in the Studio, newest first (the same Library the web app shows). Returns served URLs you can open directly or hand to fetch_asset for a download link, plus each asset's kind, model, and age. Free, read-only.",
|
|
1226
|
+
inputSchema: {
|
|
1227
|
+
kind: z.enum(['image', 'video', 'all']).optional().describe("filter by asset kind (default 'all')"),
|
|
1228
|
+
limit: z.number().optional().describe('max assets to return (default 20, max 60)'),
|
|
1229
|
+
},
|
|
1230
|
+
outputSchema: { assets: z.array(z.object({ url: z.string(), kind: z.string().optional(), model: z.string().optional(), ageHours: z.number().optional() })).optional() },
|
|
1231
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1232
|
+
}, wrap(async (a) => {
|
|
1233
|
+
const ak = PROFILE !== 'default' ? `heist.assets.v1.${PROFILE}` : 'heist.assets.v1';
|
|
1234
|
+
let list = [];
|
|
1235
|
+
try { list = JSON.parse((await apiGet(`/api/store/${encodeURIComponent(ak)}`))?.value || 'null') || []; } catch {}
|
|
1236
|
+
if (!Array.isArray(list)) list = [];
|
|
1237
|
+
const kind = a.kind && a.kind !== 'all' ? a.kind : null;
|
|
1238
|
+
const lim = Math.min(60, Math.max(1, +a.limit || 20));
|
|
1239
|
+
const assets = list.filter(x => x && x.url && (!kind || x.kind === kind)).slice(0, lim)
|
|
1240
|
+
.map(x => ({ url: /^https?:/.test(x.url) ? x.url : `${API_BASE}${x.url}`, kind: x.kind || '', model: x.model || '', ageHours: x.at ? Math.round((Date.now() - x.at) / 36e5) : undefined }));
|
|
1241
|
+
if (!assets.length) return ok('The Library is empty for this workspace — render something first.', { assets: [] });
|
|
1242
|
+
return ok(`${assets.length} asset${assets.length === 1 ? '' : 's'} (newest first):\n` + assets.map((x, i) => ` ${i + 1}. [${x.kind || '?'}${x.model ? ' · ' + x.model : ''}${x.ageHours != null ? ' · ' + x.ageHours + 'h ago' : ''}] ${x.url}`).join('\n'), { assets });
|
|
1243
|
+
}));
|
|
1244
|
+
|
|
1190
1245
|
server.registerTool('fetch_asset', {
|
|
1191
1246
|
title: 'Fetch asset',
|
|
1192
1247
|
description: 'Resolve a generated asset reference (a /generated/… path or any URL) to a clickable absolute URL + a direct download URL.',
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
|
-
"description": "Generate finished VIDEO ADS, image ads and UGC avatar ads for any brand with AI
|
|
5
|
+
"description": "Generate finished VIDEO ADS, image ads and UGC avatar ads for any brand with AI — and spy on competitor ads across the Meta, Google and LinkedIn ad libraries plus TikTok/Instagram/YouTube organic. MCP server, 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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"hermoso": "bin/hermoso.mjs"
|