hermoso 0.1.141 → 0.1.142

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.
Files changed (2) hide show
  1. package/mcp/tools.mjs +32 -4
  2. package/package.json +1 -1
package/mcp/tools.mjs CHANGED
@@ -469,6 +469,28 @@ const AD_RESULT_HTML = String.raw`<div id="root"></div>
469
469
  var POLL_MS = 5000, FIRST_MS = 1500, MAX_POLLS = 72, MAX_MISSES = 3; // 72 x 5s = 6 min, past any render this card carries
470
470
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return '&#' + c.charCodeAt(0) + ';'; }); }
471
471
  function looksVideo(u) { return /\.(mp4|webm|mov|m4v)([?#]|$)/i.test(String(u || '')); }
472
+ function urlOf(x) { return typeof x === 'string' ? x : ((x && (x.image || x.url || x.src)) || ''); }
473
+ function picks(out, raw) {
474
+ var lists = [raw.images, out.images, out.thumbnails, raw.thumbnails];
475
+ for (var i = 0; i < lists.length; i++) {
476
+ if (!Array.isArray(lists[i])) continue;
477
+ var u = lists[i].map(urlOf).filter(Boolean);
478
+ if (u.length) return u;
479
+ }
480
+ return null;
481
+ }
482
+ // NOTHING TO SHOW IS NOT A CARD (2026-08-23). Driving Hermoso inside ChatGPT, Dave's conversation filled up with
483
+ // empty Hermoso cards reading "No media in this result yet." — a full-height bordered box with a wordmark and no
484
+ // content, sometimes three or four in a row. Every widget-bound tool draws this card on EVERY call, and several
485
+ // of their results legitimately carry no media at all: render_ad's dryRun and its needsProductPhoto ask,
486
+ // generate_video's over-length refusal, make_thumbnail's two ask-first gates, a get_job on a job whose type has
487
+ // no media, and ANY tool throw (wrap() answers {content,isError} with no structuredContent at all, so the widget
488
+ // is handed {}). In every one of those the model's own text already says what happened, so the card adds a box
489
+ // and no information. The three states that MUST stay visible are unchanged — media, a pending render, and a
490
+ // failure — and only the fourth, "nothing", collapses to literally nothing.
491
+ function collapse() {
492
+ try { var api = window.openai; if (api && typeof api.notifyIntrinsicHeight === 'function') api.notifyIntrinsicHeight(0); } catch (e) {}
493
+ }
472
494
  // THREE RESULT SHAPES REACH THIS ONE WIDGET and only one of them is flat. Reading just the flat one is why a
473
495
  // finished job rendered as "No media": renderJob nests under .raw, but get_job returns the JOB, whose payload is
474
496
  // at .result.data. One resolver, so a field added to either reader is found by both.
@@ -486,9 +508,14 @@ const AD_RESULT_HTML = String.raw`<div id="root"></div>
486
508
  // creditsUsed sits BESIDE data on a job envelope ({result:{data:{…}, creditsUsed}}), not inside it — read off a real job row.
487
509
  var c = out.creditsUsed != null ? out.creditsUsed : (out.credits != null ? out.credits : (raw.creditsUsed != null ? raw.creditsUsed : (out.result ? out.result.creditsUsed : null)));
488
510
  v.credits = c;
489
- v.slides = (Array.isArray(raw.images) && raw.images.length) ? raw.images : ((Array.isArray(out.images) && out.images.length) ? out.images : null);
511
+ // A PICTURE SET ARRIVES UNDER THREE NAMES AND IN TWO SHAPES, and reading only images[] is why every
512
+ // SUCCESSFUL make_thumbnail call painted "No media in this result yet." (2026-08-23): that tool answers
513
+ // {thumbnails:[{image,label,…}]} — a real, delivered, PAID render this card threw away. So resolve the first
514
+ // non-empty list out of all four spellings and map each entry to its url, string or object alike.
515
+ v.slides = picks(out, raw);
516
+ if (v.slides && v.slides.length === 1) { v.image = v.slides[0]; v.slides = null; } // one picture is a picture, not a one-tile grid
490
517
  v.video = out.video || raw.video || null;
491
- v.image = out.image || raw.image || null;
518
+ v.image = v.image || out.image || raw.image || null;
492
519
  v.poster = out.poster || raw.poster || null;
493
520
  var any = out.url || raw.url || null;
494
521
  if (!v.video && !v.image && any) { if (looksVideo(any)) { v.video = any; } else { v.image = any; } }
@@ -524,7 +551,7 @@ const AD_RESULT_HTML = String.raw`<div id="root"></div>
524
551
  if (v.credits != null) pills += '<span class="pill">' + esc(v.credits) + ' credits</span>';
525
552
  var footer = '<div class="meta">' + pills + '<span class="spacer"></span><span class="wordmark">Hermoso</span></div>';
526
553
  var body;
527
- if (v.slides) body = '<div class="grid">' + v.slides.map(function (u) { return '<img src="' + esc(u) + '" alt="carousel slide" loading="lazy">'; }).join('') + '</div>';
554
+ if (v.slides) body = '<div class="grid">' + v.slides.map(function (u) { return '<img src="' + esc(u) + '" alt="rendered image" loading="lazy">'; }).join('') + '</div>';
528
555
  else if (v.video) body = '<div class="media"><video controls muted autoplay loop playsinline preload="metadata"' + (v.poster ? ' poster="' + esc(v.poster) + '"' : '') + ' src="' + esc(v.video) + '"></video></div>';
529
556
  else if (v.image) body = '<div class="media"><img src="' + esc(v.image) + '" alt="generated ad"></div>';
530
557
  else if (v.error) body = '<div class="empty bad">This render did not finish: ' + esc(v.error) + '</div>';
@@ -536,7 +563,8 @@ const AD_RESULT_HTML = String.raw`<div id="root"></div>
536
563
  else note = 'Checking every few seconds; this card updates itself the moment it is ready.';
537
564
  body = '<div class="pending"><span class="spin"></span><span>Rendering' + (v.jobId ? ' — job ' + esc(v.jobId) : '') + '. Video renders take 1–3 minutes.<span class="sub">' + note + '</span></span></div>';
538
565
  }
539
- else body = '<div class="empty">No media in this result yet.</div>';
566
+ // …and with no media, no pending render and no failure there is nothing to draw: paint NOTHING (see collapse()).
567
+ if (!body) { root.innerHTML = ''; stop(); collapse(); return; }
540
568
  root.innerHTML = '<div class="card">' + body + footer + '</div>';
541
569
  if (v.pending) { schedule(); } else { stop(); }
542
570
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.141",
3
+ "version": "0.1.142",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
5
  "description": "AI ad studio and marketing MCP server with 681 tools. Research the ads already running in any market, generate finished image, video and UGC avatar ads, publish and schedule them to your own channels, build and manage the ad campaigns behind them, and read what they achieved. 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",