hermoso 0.1.530 → 0.1.531

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/README.md CHANGED
@@ -67,7 +67,7 @@ Ask in your own words. The agent picks the tools:
67
67
  - "Compare last week's Google Ads and Meta Ads results and tell me what to pause."
68
68
  - "Schedule this video to TikTok, Instagram Reels and YouTube Shorts for Tuesday at 9am."
69
69
 
70
- **1195 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
70
+ **1198 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
71
71
  catalog with exact per-render credit costs plus the full capability map.
72
72
 
73
73
  **No markup on models** on Starter, Pro and Studio. Stop overpaying markups on AI models. Plans: [hermoso.ai/pricing](https://hermoso.ai/pricing).
@@ -304,7 +304,7 @@ block entirely if you signed in above; it is there for CI, where the process can
304
304
 
305
305
  Then ask your agent: *“Generate an image ad with Hermoso.”*
306
306
 
307
- ### What the 1195 tools cover
307
+ ### What the 1198 tools cover
308
308
 
309
309
  **Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
310
310
  Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
package/mcp/tools.mjs CHANGED
@@ -3061,7 +3061,7 @@ export const widgetHostHides = (name, widgetHost) => (widgetHost
3061
3061
  // self-describing schema, and the default roster sits at its token ceiling. find_tools finds these, call_tool or a
3062
3062
  // direct tools/call runs them, and enable_tools({groups:['create']}) (or 'all', or ?tools=all) lists them. post_edit's description names
3063
3063
  // edit_timeline, and edit_timeline's names video_frames, so a caller that starts from post_edit reaches both.
3064
- export const ON_DEMAND_TOOLS = new Set(['save_location', 'delete_location', 'edit_timeline', 'video_frames', 'recast_hook', 'face_check', 'list_templates', 'finish_draft', 'make_static_ads', 'make_listing_set', 'reangle_video', 'repurpose_transcript', 'tiktok_ideas', 'generate_sfx', 'repurpose_image', 'add_presenter', 'list_products', 'add_product', 'refresh_products', 'update_product', 'remove_product', 'make_short', 'finish_short', 'rewrite_ad_line', 'save_video_preset', 'list_video_presets', 'update_video_preset', 'delete_video_preset', 'make_from_preset', 'list_winning_ads']); // rewrite_ad_line (2026-11-08): multiply_ad's description and its change note name it // list_winning_ads (2026-11-08): multiply_ad's description names it // the five video-preset tools (2026-11-08): clone_video's description names save_video_preset, whose reply names make_from_preset; list_video_presets names the rest // make_short / finish_short (2026-11-08): clone_video's description names make_short, and make_short's description and reply name finish_short // make_static_ads (2026-10-06): plan_variations' description names it, so a caller planning variants reaches it; // recast_hook and face_check (2026-09-24): hook_variants names recast_hook, and recast_hook's reply names face_check // finish_draft (2026-10-03): every 480p Seedance 2.5 video reply names it (draftLine) // generate_sfx (2026-11-03): find_sound's and generate_voice's descriptions name it
3064
+ export const ON_DEMAND_TOOLS = new Set(['submit_to_gallery', 'list_gallery_submissions', 'withdraw_from_gallery', 'save_location', 'delete_location', 'edit_timeline', 'video_frames', 'recast_hook', 'face_check', 'list_templates', 'finish_draft', 'make_static_ads', 'make_listing_set', 'reangle_video', 'repurpose_transcript', 'tiktok_ideas', 'generate_sfx', 'repurpose_image', 'add_presenter', 'list_products', 'add_product', 'refresh_products', 'update_product', 'remove_product', 'make_short', 'finish_short', 'rewrite_ad_line', 'save_video_preset', 'list_video_presets', 'update_video_preset', 'delete_video_preset', 'make_from_preset', 'list_winning_ads']); // rewrite_ad_line (2026-11-08): multiply_ad's description and its change note name it // list_winning_ads (2026-11-08): multiply_ad's description names it // the five video-preset tools (2026-11-08): clone_video's description names save_video_preset, whose reply names make_from_preset; list_video_presets names the rest // make_short / finish_short (2026-11-08): clone_video's description names make_short, and make_short's description and reply name finish_short // make_static_ads (2026-10-06): plan_variations' description names it, so a caller planning variants reaches it; // recast_hook and face_check (2026-09-24): hook_variants names recast_hook, and recast_hook's reply names face_check // finish_draft (2026-10-03): every 480p Seedance 2.5 video reply names it (draftLine) // generate_sfx (2026-11-03): find_sound's and generate_voice's descriptions name it
3065
3065
 
3066
3066
  // ── A TOOL NAME A HOST STILL HOLDS MUST KEEP ANSWERING (2026-09-14) ─────────────────────────────────────────────
3067
3067
  // ChatGPT users get the tool roster OpenAI SNAPSHOTTED at review time, never a live tools/list (memory:
@@ -6671,7 +6671,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
6671
6671
  verifiedAccount: z.boolean().optional().describe('true = verified accounts only'), hasPortfolio: z.boolean().optional().describe('true = creators with a portfolio only'), hasPublicContactEmail: z.boolean().optional().describe('true = creators with a public contact email only'),
6672
6672
  featuredInPaidAds: z.boolean().optional().describe('true = creators featured in paid ads only'), excludeMessagedCreators: z.boolean().optional().describe('true = hide creators this brand has already messaged'),
6673
6673
  limit: z.number().optional().describe('1 to 50'), cursor: z.string().optional().describe('next page: the cursor value from the previous reply'),
6674
- pageId: z.string().optional().describe("Facebook Page id linked to the brand's Instagram; from list_meta_pages"), account: z.string().optional().describe('direct Instagram account (@handle or id) when several are connected; from list_connector_accounts'),
6674
+ pageId: z.string().optional().describe("Facebook Page id linked to the brand's Instagram; from list_meta_pages; if omitted, the Page that can search is used"), account: z.string().optional().describe('direct Instagram account (@handle or id) when several are connected; from list_connector_accounts'),
6675
6675
  },
6676
6676
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
6677
6677
  }, wrap(async (a) => {
@@ -27289,6 +27289,57 @@ function memoryNoteVerdict(text) {
27289
27289
  return ok(`${d?.message || ''}${items ? '\n' + items : ''}${kept ? '\n' + kept : ''}${nf}`, { deleted: !!d?.deleted, needsConfirm: !!d?.needsConfirm });
27290
27290
  }));
27291
27291
 
27292
+ // THE PUBLIC GALLERY (2026-11-10, lib/gallery.mjs): opt-in per item, reviewed by the Hermoso team before it is public,
27293
+ // withdrawable at once. Held on demand (ON_DEMAND_TOOLS); each reply names the next tool.
27294
+ const galleryUrlOf = async (id, url) => {
27295
+ if (url) return String(url);
27296
+ const m = /^L(\d+)$/i.exec(String(id || '').trim()); if (!m) return '';
27297
+ const list = await readStore('heist.assets.v1');
27298
+ return (Array.isArray(list) ? list.filter((x) => x && x.url) : [])[Number(m[1])]?.url || '';
27299
+ };
27300
+ server.registerTool('submit_to_gallery', {
27301
+ title: 'Submit to the Hermoso gallery',
27302
+ description: "Submit ONE image or video this brand made in Hermoso to the public Hermoso gallery. Only when the user asks. They must confirm they have the rights to it, including any people and logos in it (rights:true); their brand name shows only with showBrand:true. Nothing is public until the Hermoso team approves it. preview:true returns what would be public and saves nothing. Free.",
27303
+ inputSchema: {
27304
+ id: z.string().optional().describe('Library id (L0…) from list_library'),
27305
+ url: z.string().optional().describe('or the file link'),
27306
+ rights: z.boolean().optional().describe('true only after the user confirmed the rights'),
27307
+ showBrand: z.boolean().optional().describe('show the brand name (default false)'),
27308
+ title: z.string().optional(),
27309
+ format: z.enum(['ugc_video', 'product_video', 'talking_avatar', 'motion', 'product_photo', 'static_ad', 'carousel', 'other']).optional(),
27310
+ niche: z.enum(['beauty', 'fashion', 'food', 'fitness', 'health', 'tech', 'home', 'pets', 'finance', 'education', 'travel', 'local', 'other']).optional(),
27311
+ preview: z.boolean().optional(),
27312
+ },
27313
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
27314
+ }, wrap(async ({ id, url, rights, showBrand, title, format, niche, preview }) => {
27315
+ const u = await galleryUrlOf(id, url);
27316
+ if (!u) return ok('Pass a Library id (L0… from list_library) or the file link. Nothing was submitted.', { submitted: false });
27317
+ const d = await apiPost('/api/gallery/submit', { url: u, rights: rights === true, showBrand: showBrand === true, ...(title ? { title } : {}), ...(format ? { format } : {}), ...(niche ? { niche } : {}), ...(preview ? { preview: true } : {}) });
27318
+ if (d?.preview) { const p = d.public || {}; return ok(`Would be public:\n Title: ${p.title}\n Brand: ${p.brandShown || 'hidden'}\n${(p.recipe || []).map((r) => ` ${r.k}: ${r.v}`).join('\n')}\nNothing was saved. Ask the user to confirm the rights, then call again with rights:true.`, { preview: true, public: p }); }
27319
+ return ok(`${d?.message || 'Submitted.'} Status: ${d?.status}${d?.publicUrl ? ` · ${d.publicUrl}` : ''} (id ${d?.id}). list_gallery_submissions shows it; withdraw_from_gallery takes it down.`, { submitted: true, id: d?.id, status: d?.status, publicUrl: d?.publicUrl || null });
27320
+ }));
27321
+ server.registerTool('list_gallery_submissions', {
27322
+ title: 'List gallery submissions',
27323
+ description: "This brand's submissions to the public Hermoso gallery: each one's id, status (pending review, approved with its public page, rejected) and file. Free.",
27324
+ inputSchema: { all: z.boolean().optional().describe('include withdrawn ones') },
27325
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
27326
+ }, wrap(async ({ all }) => {
27327
+ const d = await apiGet('/api/gallery/mine', all ? { all: '1' } : {});
27328
+ const rows = d?.submissions || [];
27329
+ if (!rows.length) return ok('Nothing from this brand is in the gallery or waiting for review. submit_to_gallery adds one when the user asks.', { submissions: [] });
27330
+ return ok(rows.map((r) => `${r.id} · ${r.status}${r.publicUrl ? ` · ${r.publicUrl}` : ''} · ${r.title} · ${abs(r.url)}`).join('\n') + '\nwithdraw_from_gallery takes one down.', { submissions: rows });
27331
+ }));
27332
+ server.registerTool('withdraw_from_gallery', {
27333
+ title: 'Withdraw from the gallery',
27334
+ description: 'Take a submission off the public Hermoso gallery at once (its page stops working), whether pending or approved. Pass the submission id from list_gallery_submissions, or the file link. Free.',
27335
+ inputSchema: { id: z.string().optional().describe('submission id (g_…)'), url: z.string().optional().describe('or the file link') },
27336
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
27337
+ }, wrap(async ({ id, url }) => {
27338
+ if (!id && !url) return ok('Pass the submission id (list_gallery_submissions) or the file link. Nothing was changed.', { withdrawn: [] });
27339
+ const d = await apiPost('/api/gallery/withdraw', { ...(id ? { id } : {}), ...(url ? { url } : {}) });
27340
+ return ok(d?.message || 'Withdrawn.', { withdrawn: d?.withdrawn || [] });
27341
+ }));
27342
+
27292
27343
  server.registerTool('fetch_asset', {
27293
27344
  title: 'Fetch asset',
27294
27345
  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.530",
3
+ "version": "0.1.531",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
- "description": "AI marketing agent and AI ad generator, run from your own AI agent. 1195 tools. No markup on models on Starter and up. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on any plan; Free includes 5 posts a month and every paid plan posts without a limit. 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, WordPress, Discord, Slack, Whop 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.",
5
+ "description": "AI marketing agent and AI ad generator, run from your own AI agent. 1198 tools. No markup on models on Starter and up. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on any plan; Free includes 5 posts a month and every paid plan posts without a limit. 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, WordPress, Discord, Slack, Whop 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",
7
7
  "bin": {
8
8
  "hermoso": "bin/hermoso.mjs"