hermoso 0.1.377 → 0.1.379
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/mcp/tools.mjs +18 -15
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -5694,7 +5694,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
5694
5694
|
// never re-posts the Library; in 'review' mode each batch waits as drafts until approved.
|
|
5695
5695
|
server.registerTool('get_post_refill', {
|
|
5696
5696
|
title: 'Autoposting status',
|
|
5697
|
-
description: 'Show autoposting (the posting refill) for this profile: whether it is on, its mode (review = each batch of fresh posts waits as drafts for approval; auto = fresh posts are scheduled straight away),
|
|
5697
|
+
description: 'Show autoposting (the posting refill) for this profile: whether it is on, its mode (review = each batch of fresh posts waits as drafts for approval; auto = fresh posts are scheduled straight away), WHICH CHANNELS it posts to with each one’s posts per day and posting times, roughly what it costs a day, when it next runs, how many posts are queued, and the DRAFTS waiting for approval (id, time, channels, caption, the new image or video). It also names the channels that CANNOT be posted to and why. Read-only, free.',
|
|
5698
5698
|
inputSchema: {},
|
|
5699
5699
|
outputSchema: {
|
|
5700
5700
|
enabled: z.boolean().optional(), mode: z.string().optional(), daysAhead: z.number().optional(),
|
|
@@ -5705,14 +5705,14 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
5705
5705
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
5706
5706
|
}, wrap(async () => {
|
|
5707
5707
|
const d = await apiGet('/api/schedule/refill', {});
|
|
5708
|
-
const cadence = d.postsPerDay > 0 ? d.postsPerDay : (d.postingSchedule?.slots || []).length;
|
|
5709
5708
|
const state = !d.enabled ? 'OFF' : (d.mode === 'auto' ? 'ON, AUTO-PUBLISH — each run makes fresh posts and schedules them' : 'ON, REVIEW — each run makes fresh posts and keeps them as drafts until you approve them');
|
|
5710
5709
|
const drafts = Array.isArray(d.drafts) ? d.drafts : [];
|
|
5711
5710
|
const draftLines = drafts.slice(0, 20).map((x, i) => ` ${i + 1}. ${x.id} · ${x.at} → ${(x.channels || []).join(', ')} · ${x.kind}\n ${String(x.message || '').replace(/\n+/g, ' / ').slice(0, 200)}\n ${x.media || '(no file)'}`);
|
|
5712
5711
|
return ok([
|
|
5713
5712
|
`Autoposting: ${state}.${d.note ? `\n${d.note}` : ''}`,
|
|
5714
|
-
|
|
5715
|
-
|
|
5713
|
+
`Posting times: ${(d.postingTimes || []).join(', ') || 'none set (add some on the Schedule tab)'} ${d.postingSchedule?.timezone || 'UTC'}. It fills ${d.daysAhead} day(s) ahead; ${d.queuedPosts} post(s) queued now.`,
|
|
5714
|
+
(d.channelsView || []).length ? `Channels:\n${d.channelsView.map((c) => ` ${c.on ? '[on] ' : '[off]'} ${c.label} (${c.channel}): ${c.on ? `${c.postsPerDay} a day${c.times.length ? ` at ${c.times.join(', ')}` : ''}` : 'not posting'}${c.note ? ` · ${c.note}` : ''}`).join('\n')}` : (d.connectedUnreadable ? 'Channels: could not be read just now.' : 'Channels: none connected — autopilot has nowhere to post. Connect one under Workspace ▸ Connectors.'),
|
|
5715
|
+
`Makes ${d.postsPerDayMade || 0} new post(s) a day, every one a fresh render made for it${d.estCreditsPerDay != null && d.postsPerDayMade ? `, about ${d.estCreditsPerDay} credits a day at today's prices` : ''}.${d.zeroBudget ? ` ${d.budgetNote}` : ''} Used: ${d.spend?.today || 0} credits today, ${d.spend?.week || 0} this week.`,
|
|
5716
5716
|
`Brief: goal ${d.brief?.goal || 'not set (required to switch it on)'}${d.brief?.goalNote ? ` (${d.brief.goalNote})` : ''} · format ${d.brief?.formats || 'mix'}${(d.brief?.pillars || []).length ? ` · pillars: ${d.brief.pillars.join('; ')}` : (d.suggestedPillars || []).length ? ` · no pillars yet; suggested from the brand: ${d.suggestedPillars.join('; ')}` : ''}${(d.brief?.avoid || []).length ? ` · avoid: ${d.brief.avoid.join('; ')}` : ''}`,
|
|
5717
5717
|
d.suggestAuto ? `${d.approvedBatches} review batches approved so far: auto-publish (mode "auto") is available if the user wants posts to go out without a review step. Ask; never switch it yourself.` : '',
|
|
5718
5718
|
drafts.length ? `Drafts waiting for approval (${drafts.length}):\n${draftLines.join('\n')}${drafts.length > 20 ? `\n …and ${drafts.length - 20} more` : ''}\nApprove, edit or discard them with run_post_refill (approve / edit / discard).` : 'No drafts waiting for approval.',
|
|
@@ -5721,20 +5721,17 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
5721
5721
|
}));
|
|
5722
5722
|
server.registerTool('set_post_refill', {
|
|
5723
5723
|
title: 'Configure autoposting',
|
|
5724
|
-
description: 'Turn autoposting (the posting refill) on or off and set how it behaves. PASS ONLY WHAT CHANGES. It always makes NEW posts (a fresh image, carousel or video for each)
|
|
5724
|
+
description: 'Turn autoposting (the posting refill) on or off and set how it behaves. PASS ONLY WHAT CHANGES. It always makes NEW posts (a fresh image, carousel or video for each) and never re-posts the Library. WHERE: every connected channel by default (channels narrows it); HOW OFTEN: the brand’s posting times, and channelPostsPerDay sets fewer on a channel (e.g. {"x":1}). There are no credit limits to set — get_post_refill shows roughly what it costs a day. Switching it on is refused when no channel is connected or nothing a day would be made. SWITCHING IT ON ASKS THE BRIEF ONCE: a goal (followers / sales / launch / other) is required; content pillars (get_post_refill suggests some from the brand), formats (mix / video / carousel / image) and an avoid list are optional. Posts are written from the brief, the brand profile, what has worked on each channel (compared only within a channel), the brand’s skills and playbooks and the ads it saved. Ask the user for anything missing (goal, mode, channels) rather than guessing. `mode` picks what happens to each batch: "review" (the default) keeps the fresh posts as drafts until a human approves them (run_post_refill approve), "auto" schedules them straight away. `enabled:false` is the PAUSE — it removes the recurring job outright, and posts already queued are left alone (cancel those with cancel_scheduled). Three posting times means up to three posts a day; to post MORE per day, add posting times on the Schedule tab.',
|
|
5725
5725
|
inputSchema: {
|
|
5726
|
-
enabled: z.boolean().optional().describe('on/off. true needs a
|
|
5726
|
+
enabled: z.boolean().optional().describe('on/off. true needs a connected channel and a goal (refused otherwise). false PAUSES it: the recurring job is deleted and nothing new is made. Already-queued posts are untouched.'),
|
|
5727
5727
|
mode: z.enum(['review', 'auto']).optional().describe('"review" (default): each batch of fresh posts waits as drafts for approval. "auto": fresh posts are scheduled to publish automatically.'),
|
|
5728
5728
|
daysAhead: z.number().optional().describe('how far ahead to keep the queue full, 1–30 (default 7)'),
|
|
5729
5729
|
postsPerDay: z.number().optional().describe('cap the posts per day BELOW the number of posting times. 0 (default) = use every posting time, which is where "3 a day" comes from. To post MORE per day, add posting times instead.'),
|
|
5730
|
-
|
|
5731
|
-
maxVideosPerDay: z.number().optional().describe('how many NEW short videos a day it may render. 0 = none. Video is the expensive one.'),
|
|
5732
|
-
maxCreditsPerDay: z.number().optional().describe('a hard credit ceiling per day, checked BEFORE any render starts. It binds independently of the counts above, and 0 means it makes nothing.'),
|
|
5733
|
-
maxCreditsPerWeek: z.number().optional().describe('a credit ceiling per week (0 = none). With it, each day may use what the week has left, within any daily cap.'),
|
|
5730
|
+
channelPostsPerDay: z.record(z.number()).optional().describe('per channel, how many posts a day (the first N of the posting times), e.g. {"x":1,"instagram":3}. 0 turns a channel off; null resets it to every posting time. Merged into what is saved.'),
|
|
5734
5731
|
goal: z.enum(['followers', 'sales', 'launch', 'other']).optional().describe('THE BRIEF: what the posts are for — grow followers, drive sales, promote a launch, or other (say what in goalNote). Required to switch it on.'),
|
|
5735
5732
|
goalNote: z.string().optional().describe('THE BRIEF: a line about the goal (e.g. the launch date and what is launching).'),
|
|
5736
5733
|
pillars: z.array(z.string()).optional().describe('THE BRIEF: content pillars / topics, up to 8 short lines. Every post belongs to one. get_post_refill suggests some from the brand profile.'),
|
|
5737
|
-
formats: z.enum(['mix', 'video', 'carousel', 'image']).optional().describe('THE BRIEF: what to make — mix (images with a short video now and then, the default), video (short videos only
|
|
5734
|
+
formats: z.enum(['mix', 'video', 'carousel', 'image']).optional().describe('THE BRIEF: what to make — mix (images with a short video now and then, the default), video (short videos only), carousel (3 new images per post), image.'),
|
|
5738
5735
|
avoid: z.array(z.string()).optional().describe('THE BRIEF: never mention or show these — topics, claims, competitors, faces.'),
|
|
5739
5736
|
channels: z.array(z.enum(['facebook', 'instagram', 'threads', 'tiktok', 'youtube', 'linkedin', 'x', 'pinterest', 'google_business', 'bluesky', 'telegram'])).optional().describe('restrict it to these channels. Omit (or send an empty list) to use every connected channel that can carry each post.'),
|
|
5740
5737
|
boardId: z.string().optional().describe('PINTEREST — which board Pins go on (list_pinterest_boards). Without one, Pinterest is skipped: a Pin on the wrong board is a public mistake, so it is never guessed.'),
|
|
@@ -5749,7 +5746,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
5749
5746
|
const brief = Object.fromEntries(Object.entries({ goal, goalNote, pillars, formats, avoid }).filter(([, v]) => v !== undefined));
|
|
5750
5747
|
const d = await apiPut('/api/schedule/refill', { ...rest, ...(Object.keys(brief).length ? { brief } : {}) });
|
|
5751
5748
|
return ok(d.enabled
|
|
5752
|
-
? `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.'}
|
|
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.`
|
|
5753
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);
|
|
5754
5751
|
}));
|
|
5755
5752
|
server.registerTool('run_post_refill', {
|
|
@@ -7636,6 +7633,8 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
7636
7633
|
endTime: z.string().optional().describe('REQUIRED with lifetimeBudgetUsd'),
|
|
7637
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'),
|
|
7638
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'),
|
|
7639
7638
|
};
|
|
7640
7639
|
server.registerTool('create_meta_ad', {
|
|
7641
7640
|
title: 'Build a full Meta ad (campaign -> ad set -> ad, paused)',
|
|
@@ -17182,13 +17181,15 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17182
17181
|
status: z.enum(['ACTIVE', 'PAUSED', 'ARCHIVED']).optional().describe('ACTIVE starts spend (needs confirm:true); PAUSED / ARCHIVED are safe'),
|
|
17183
17182
|
dailyBudgetUsd: z.number().optional().describe('new daily budget in USD (1–10000; ad-set or campaign level)'),
|
|
17184
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)'),
|
|
17185
17186
|
confirm: z.boolean().optional().describe('REQUIRED true ONLY to set status ACTIVE (real spend)'),
|
|
17186
17187
|
},
|
|
17187
|
-
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') },
|
|
17188
17189
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
17189
17190
|
}, wrap(async (a) => {
|
|
17190
17191
|
const d = await apiPost('/api/meta/object/update', a);
|
|
17191
|
-
return ok(`Updated ${a.objectId} (${(d.updated || []).join(', ')})
|
|
17192
|
+
return ok(`Updated ${a.objectId} (${(d.updated || []).join(', ')}).${d.euTransparency ? ` ${d.euTransparency}` : ''}`, d);
|
|
17192
17193
|
}));
|
|
17193
17194
|
// ── LINKEDIN LEAD SYNC (2026-09-04): forms + leads read, notification webhooks managed. Standard Tier granted
|
|
17194
17195
|
// to app 257800418 that day; the consent dialog was probed for the scope BEFORE a line was written. Needs
|
|
@@ -18180,11 +18181,13 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
18180
18181
|
text: z.string().optional().describe('the generated text'),
|
|
18181
18182
|
model: z.string().optional().describe('the writing model label'),
|
|
18182
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'),
|
|
18183
18186
|
},
|
|
18184
18187
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
18185
18188
|
}, wrap(async ({ prompt, model, raw }) => {
|
|
18186
18189
|
const d = await apiPost('/api/models/llm', { prompt, ...(model ? { model } : {}), ...(raw === true ? { raw: true } : {}) });
|
|
18187
|
-
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);
|
|
18188
18191
|
}));
|
|
18189
18192
|
|
|
18190
18193
|
// ---------- video / avatar / stitch (job-based, polled to completion) ----------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.379",
|
|
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",
|