hermoso 0.1.378 → 0.1.380

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 +17 -11
  2. package/package.json +1 -1
package/mcp/tools.mjs CHANGED
@@ -4025,7 +4025,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
4025
4025
  const _resLine = 'RESOLUTION: each model\'s `resolutions` list is its REAL enum (and `creditsByRes` prices every tier). Ask for a tier a model does not list and the render is delivered at that model\'s best available tier instead — the reply does not say so — so read `resolutions` here before promising anyone 1080p or 4k.';
4026
4026
  // recast_motion's two tiers, priced off the same live quote the app's costs page reads (toolExamples), never a number here.
4027
4027
  const _mq = (q) => (q ? Object.entries(q).map(([s, c]) => `${s}s=${c}cr`).join(' ') : null);
4028
- const _motionLine = _mq(d.toolExamples?.motion) ? `\nRecast motion (recast_motion, billed per output second): tier pro (default) ${_mq(d.toolExamples.motion)}${_mq(d.toolExamples.motionStandard) ? `; tier standard ${_mq(d.toolExamples.motionStandard)}` : ''}` : '';
4028
+ const _motionLine = _mq(d.toolExamples?.motion) ? `\nRecast motion (recast_motion, billed per output second): tier pro (default) ${_mq(d.toolExamples.motion)}${_mq(d.toolExamples.motionStandard) ? `; tier standard ${_mq(d.toolExamples.motionStandard)}` : ''}${d.toolExamples.motionH3 ? `; engine h3 (a person clip recast with people) 720p ${_mq(d.toolExamples.motionH3['720p'])}, 1080p ${_mq(d.toolExamples.motionH3['1080p'])}` : ''}` : '';
4029
4029
  // THE TALKING-AVATAR ENGINES (generate_avatar `engine`), priced live per second of speech — never a number here.
4030
4030
  const _avLine = (d.avatarEngines || []).length ? `\nTalking-avatar engines (generate_avatar engine): ${(d.avatarEngines || []).map(e => `${e.id}${e.default ? ' (default)' : ''} = ${e.label}${e.id === 'natural' ? ` · ${e.model}` : ''}, ~${e.creditsPerSecond}cr per second of speech, ${(e.resolutions || []).join('/')}${e.oneAtATime ? ', renders one video at a time (about ' + e.renderSecondsPerAudioSecond + 'x the speech length plus any wait), speech under 60s' : ''}`).join('; ')}` : '';
4031
4031
  // EXTEND + RESTYLE, read off the same status (2026-10-02): which models continue a clip and by how much per call,
@@ -5746,14 +5746,14 @@ function buildTools(rawServer, opts = {}, sink = null) {
5746
5746
  const brief = Object.fromEntries(Object.entries({ goal, goalNote, pillars, formats, avoid }).filter(([, v]) => v !== undefined));
5747
5747
  const d = await apiPut('/api/schedule/refill', { ...rest, ...(Object.keys(brief).length ? { brief } : {}) });
5748
5748
  return ok(d.enabled
5749
- ? `Autoposting is ON in ${d.mode === 'auto' ? 'AUTO-PUBLISH mode — each run makes fresh posts and schedules them to go out on their own.' : 'REVIEW mode — each run makes fresh posts and keeps them as drafts; nothing is scheduled until you approve them with run_post_refill.'} get_post_refill lists the channels it posts to and what it costs a day. Run run_post_refill (a free preview by default) to see a plan.`
5749
+ ? `Autoposting is ON in ${d.mode === 'auto' ? 'AUTO-PUBLISH mode — each run makes fresh posts and schedules them to go out on their own.' : 'REVIEW mode — each run makes fresh posts and keeps them as drafts; nothing is scheduled until you approve them with run_post_refill.'} get_post_refill lists the channels it posts to and what it costs a day. Run run_post_refill (a preview by default: it renders and queues nothing, and writing its copy costs a few credits) to see a plan.`
5750
5750
  : 'Autoposting is OFF. The recurring job is gone and nothing new will be made. Posts already in the calendar are untouched — cancel those with cancel_scheduled if you want them gone.', d);
5751
5751
  }));
5752
5752
  server.registerTool('run_post_refill', {
5753
5753
  title: 'Run autoposting / review its drafts',
5754
- description: 'Run autoposting NOW instead of waiting for its daily turn, or act on the drafts it made. PREVIEW BY DEFAULT: it returns the posts it WOULD make — each new picture’s scene, the caption, the channels and the price — rendering nothing and queueing nothing. dryRun:false actually renders a fresh image or video for each post within the budget (credits are spent) and then, by mode, schedules them ("auto") or keeps them as drafts ("review"). It never re-posts the Library. REVIEWING A BATCH: pass approve (draft ids, or ["all"]) to schedule drafts, discard to delete them, edit ([{id, message?, captions?, title?, at?, channels?}]) to change one first; get_post_refill lists them. A scheduled post can still be pulled before it goes out with cancel_scheduled. Every caption is screened against the voice rules and a failing one is dropped, so a plan can come back shorter than the cadence — the reason is in the notes.',
5754
+ description: 'Run autoposting NOW instead of waiting for its daily turn, or act on the drafts it made. PREVIEW BY DEFAULT: it returns the posts it WOULD make — each new picture’s scene, the caption, the channels and the price — rendering nothing and queueing nothing. The preview still WRITES the scenes and copy with a model, which bills a few credits (the reply says how many). dryRun:false actually renders a fresh image or video for each post within the budget (credits are spent) and then, by mode, schedules them ("auto") or keeps them as drafts ("review"). It never re-posts the Library. REVIEWING A BATCH: pass approve (draft ids, or ["all"]) to schedule drafts, discard to delete them, edit ([{id, message?, captions?, title?, at?, channels?}]) to change one first; get_post_refill lists them. A scheduled post can still be pulled before it goes out with cancel_scheduled. Every caption is screened against the voice rules and a failing one is dropped, so a plan can come back shorter than the cadence — the reason is in the notes.',
5755
5755
  inputSchema: {
5756
- dryRun: z.boolean().optional().describe('default TRUE (a preview: nothing rendered, nothing queued). false renders fresh creative and schedules or drafts it per the mode.'),
5756
+ dryRun: z.boolean().optional().describe('default TRUE (a preview: nothing rendered, nothing queued; the copy it writes bills a few credits). false renders fresh creative and schedules or drafts it per the mode.'),
5757
5757
  force: z.boolean().optional().describe('plan a preview even while autoposting is switched off — useful for showing someone what it would do before they turn it on.'),
5758
5758
  approve: z.array(z.string()).optional().describe('REVIEW: draft ids to schedule, or ["all"]. A draft whose time has passed moves to the next free posting slot.'),
5759
5759
  discard: z.array(z.string()).optional().describe('REVIEW: draft ids to delete, or ["all"]. Nothing is posted for them.'),
@@ -5786,7 +5786,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
5786
5786
  return ok([
5787
5787
  d.summary || '',
5788
5788
  lines.length ? `\nThe posts:\n${lines.join('\n')}` : '',
5789
- (d.noChannels || d.zeroBudget || (d.enabled === false && !(d.posts || []).length)) ? '' : d.dryRun ? '\nNOTHING WAS RENDERED, QUEUED OR SPENT — this was a preview. Show it to the user; call again with dryRun:false only if they say yes.'
5789
+ (d.noChannels || d.zeroBudget || (d.enabled === false && !(d.posts || []).length)) ? '' : d.dryRun ? `\nNOTHING WAS RENDERED OR QUEUED — this was a preview${d.copyCredits ? ` (writing its copy cost ${d.copyCredits} credit(s))` : ''}. Show it to the user; call again with dryRun:false only if they say yes.`
5790
5790
  : (d.mode === 'auto' ? `\n${q} post(s) scheduled. Pull any of them before it goes out with cancel_scheduled.` : `\n${(d.drafts || []).length} draft(s) waiting for approval — approve, edit or discard them with run_post_refill.`),
5791
5791
  qFail.length ? `\n${qFail.length} could not be queued: ${qFail.map(x => `${x.at} — ${x.error}`).join(' · ')}` : '',
5792
5792
  (d.notes || []).length ? `\nNotes: ${d.notes.join(' · ')}` : '',
@@ -7633,6 +7633,8 @@ function buildTools(rawServer, opts = {}, sink = null) {
7633
7633
  endTime: z.string().optional().describe('REQUIRED with lifetimeBudgetUsd'),
7634
7634
  adsetSchedule: z.array(z.object({ startMinute: z.number(), endMinute: z.number(), days: z.array(z.number()) })).optional().describe('dayparting — minutes from midnight (0–1440), days 0=Sunday…6=Saturday'),
7635
7635
  attributionSpec: z.array(z.any()).optional().describe('e.g. [{event_type:"CLICK_THROUGH",window_days:7}]'),
7636
+ dsaBeneficiary: z.string().optional().describe("EU AD TRANSPARENCY: the person or organisation this ad promotes (Meta shows it on the ad). Meta requires it whenever the ad set reaches the EU; omit it and Hermoso uses the ad account's own default, else this profile's brand name, else the Page name, and the summary says which was used"),
7637
+ dsaPayor: z.string().optional().describe('EU AD TRANSPARENCY: who pays for the ads (Meta shows it on the ad). Omit it and it matches dsaBeneficiary'),
7636
7638
  };
7637
7639
  server.registerTool('create_meta_ad', {
7638
7640
  title: 'Build a full Meta ad (campaign -> ad set -> ad, paused)',
@@ -17179,13 +17181,15 @@ function buildTools(rawServer, opts = {}, sink = null) {
17179
17181
  status: z.enum(['ACTIVE', 'PAUSED', 'ARCHIVED']).optional().describe('ACTIVE starts spend (needs confirm:true); PAUSED / ARCHIVED are safe'),
17180
17182
  dailyBudgetUsd: z.number().optional().describe('new daily budget in USD (1–10000; ad-set or campaign level)'),
17181
17183
  targeting: z.record(z.any()).optional().describe('replacement targeting spec (ad sets) — a Meta targeting object'),
17184
+ dsaBeneficiary: z.string().optional().describe("ad sets: EU ad transparency, who the ad promotes. Meta requires it once targeting reaches the EU; retarget into the EU without it and the ad account default, else the profile's brand name, else the Page name is used"),
17185
+ dsaPayor: z.string().optional().describe('ad sets: EU ad transparency, who pays (defaults to dsaBeneficiary)'),
17182
17186
  confirm: z.boolean().optional().describe('REQUIRED true ONLY to set status ACTIVE (real spend)'),
17183
17187
  },
17184
- outputSchema: { ok: z.boolean().optional(), objectId: z.string().optional(), updated: z.array(z.string()).optional() },
17188
+ outputSchema: { ok: z.boolean().optional(), objectId: z.string().optional(), updated: z.array(z.string()).optional(), euTransparency: z.string().optional().describe('the EU advertiser / payer names set on this edit, and where they came from') },
17185
17189
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
17186
17190
  }, wrap(async (a) => {
17187
17191
  const d = await apiPost('/api/meta/object/update', a);
17188
- return ok(`Updated ${a.objectId} (${(d.updated || []).join(', ')}).`, d);
17192
+ return ok(`Updated ${a.objectId} (${(d.updated || []).join(', ')}).${d.euTransparency ? ` ${d.euTransparency}` : ''}`, d);
17189
17193
  }));
17190
17194
  // ── LINKEDIN LEAD SYNC (2026-09-04): forms + leads read, notification webhooks managed. Standard Tier granted
17191
17195
  // to app 257800418 that day; the consent dialog was probed for the scope BEFORE a line was written. Needs
@@ -18177,11 +18181,13 @@ function buildTools(rawServer, opts = {}, sink = null) {
18177
18181
  text: z.string().optional().describe('the generated text'),
18178
18182
  model: z.string().optional().describe('the writing model label'),
18179
18183
  creditsUsed: z.number().optional().describe('credits billed for this generation'),
18184
+ modelRequested: z.string().optional().describe('present only when the model you named is no longer offered: the id you asked for'),
18185
+ modelNote: z.string().optional().describe('present only when the model you named is no longer offered: says the default ran instead'),
18180
18186
  },
18181
18187
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
18182
18188
  }, wrap(async ({ prompt, model, raw }) => {
18183
18189
  const d = await apiPost('/api/models/llm', { prompt, ...(model ? { model } : {}), ...(raw === true ? { raw: true } : {}) });
18184
- return ok(`${d.text}${d.model ? `\n\n— ${d.model}` : ''}`, d);
18190
+ return ok(`${d.text}${d.model ? `\n\n— ${d.model}` : ''}${d.modelNote ? `\n\n${d.modelNote}` : ''}`, d);
18185
18191
  }));
18186
18192
 
18187
18193
  // ---------- video / avatar / stitch (job-based, polled to completion) ----------
@@ -21102,14 +21108,14 @@ function memoryNoteVerdict(text) {
21102
21108
 
21103
21109
  server.registerTool('recast_motion', {
21104
21110
  title: 'Recast motion',
21105
- description: "Motion transfer: re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; the clip's sound is kept. image (+ images, named @Image1, @Image2… in prompt) supply who and what. engine auto (default): seedance (Seedance 2.5, ≤9 pictures, 4-30s, keeps the set and every beat; a person clip needs it to be our render or faceRoute, else wan ≤15s / kling). kling = Kling Motion Control (ONE picture, its background becomes the set, 3-30s); wan = Wan 3.0 Prime (≤9 pictures, keeps the set, ≤15s). Billed per output second; ~5 min for 5s; dryRun quotes it.",
21111
+ description: "Motion transfer: re-perform a clip's motion, camera and timing with YOUR character, product, clothes or place; the clip's sound is kept. image (+ images, named @Image1, @Image2… in prompt) supply who and what. engine auto (default): a person clip recast with photos of people -> h3 (H3 Max Recast: ≤4 photos, one per person, 5-30s, no shot over 15s, keeps the set, light, cuts and every beat); otherwise seedance (Seedance 2.5, ≤9 pictures, 4-30s, keeps the set and every beat; a person clip needs it to be our render or faceRoute, else wan ≤15s / kling). kling = Kling Motion Control (ONE picture, its background becomes the set, 3-30s); wan = Wan 3.0 Prime (≤9 pictures, keeps the set, ≤15s). Billed per output second; ~5 min for 5s; dryRun quotes it.",
21106
21112
  inputSchema: {
21107
21113
  image: z.string().optional().describe('who performs it: the actor/character image URL (@Image1). Required on kling. ' + LIKENESS_TERMS),
21108
21114
  images: z.array(z.string()).max(8).optional().describe('seedance/wan: more pictures, @Image2…: a character, product, outfit or place'),
21109
21115
  video: z.string().describe('the reference video whose motion to re-perform'),
21110
21116
  prompt: z.string().optional().describe("optional, e.g. 'same moves, new location: Tokyo at night'"),
21111
- engine: z.enum(['auto', 'seedance', 'wan', 'kling']).optional().describe('default auto'),
21112
- resolution: z.enum(['480p', '720p', '1080p']).optional().describe('seedance and wan; default 1080p'),
21117
+ engine: z.enum(['auto', 'h3', 'seedance', 'wan', 'kling']).optional().describe('default auto'),
21118
+ resolution: z.enum(['480p', '720p', '1080p']).optional().describe('seedance and wan default 1080p; h3 takes 720p or 1080p, default the clip’s own'),
21113
21119
  orientation: z.enum(['video', 'image']).optional().describe("kling only: which aspect to keep: the video's (default) or the image's"),
21114
21120
  faceRoute: z.enum(['face_lane']).optional().describe(FACE_ROUTE_CLIP_DESC),
21115
21121
  tier: z.enum(['pro', 'standard']).optional().describe("kling only: 'pro' (default): 1080p and real hand-object interaction. 'standard': about 25% fewer credits and faster, but 720p, and it tends to mime a held object with empty hands. hermoso_capabilities lists the exact credits for both"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.378",
3
+ "version": "0.1.380",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
5
  "description": "Marketing on autopilot, run from your own AI agent. 897 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 and Telegram. 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",