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 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 --token <key> # key from app.hermoso.ai Settings Agents & API
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.8",
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 \u2014 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.",
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"