hermoso 0.1.406 → 0.1.408
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 +16 -10
- package/mcp/openai-extensions.mjs +1 -1
- package/mcp/roster-scope.mjs +2 -0
- package/mcp/tools.mjs +12 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,9 +21,9 @@ charges per API request.
|
|
|
21
21
|
|
|
22
22
|
**What it connects to.** Ad platforms: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads,
|
|
23
23
|
Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads, ChatGPT Ads and AppLovin Ads, plus
|
|
24
|
-
product feeds in Google Merchant Center. Publishing and scheduling — **
|
|
25
|
-
Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and
|
|
26
|
-
person, so it is not
|
|
24
|
+
product feeds in Google Merchant Center. Publishing and scheduling — **eleven** channels: Facebook, Instagram,
|
|
25
|
+
Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky, Telegram and Google Business Profile. Messaging: WhatsApp (you message a
|
|
26
|
+
person, so it is not a twelfth publishing channel) and DM automations on Instagram, Facebook Pages and
|
|
27
27
|
Messenger, and X (a keyword comment, a DM, a story reply or an ig.me / m.me link gets an automatic reply). Ad research: the Meta, Google and LinkedIn ad libraries plus organic TikTok,
|
|
28
28
|
Instagram, YouTube, Threads and Reddit. Analytics: Google Analytics 4, Google Search Console and every
|
|
29
29
|
connected platform's own post and campaign insights. Files: Google Drive, Sheets, Docs and OneDrive.
|
|
@@ -271,21 +271,27 @@ Real faces from your own photos need a paid plan; AI people work on every plan.
|
|
|
271
271
|
per-render credit cost) with no ad framing: `generate_image` / `generate_video` with `useBrand:false`,
|
|
272
272
|
`generate_voice`, `generate_text`. The image default is Nano Banana 2.1.
|
|
273
273
|
|
|
274
|
-
**Publish to your own channels** — **
|
|
274
|
+
**Publish to your own channels** — **eleven** of them: Facebook, Instagram and Threads (`post_to_meta`), TikTok
|
|
275
275
|
(`post_to_tiktok`), YouTube (`post_to_youtube` + `update_youtube_video`, `youtube_video_insights`, comments
|
|
276
276
|
read/reply), X (`post_to_x`, `x_post_metrics`, `x_post_insights`, `x_mentions`, `list_x_dms`, `send_x_dm`),
|
|
277
277
|
LinkedIn profile **and** company Pages (`post_to_linkedin`, `post_to_linkedin_page`), Pinterest
|
|
278
278
|
(`post_to_pinterest` + boards), Bluesky (`post_to_bluesky`, `delete_bluesky_post`, `bluesky_post_metrics`, plus
|
|
279
279
|
`list_bluesky_convos` / `read_bluesky_dm` / `send_bluesky_dm`) and Telegram (`post_to_telegram`,
|
|
280
|
-
`delete_telegram_message`, `list_telegram_chats`). `schedule_post` / `list_scheduled` / `cancel_scheduled` give
|
|
280
|
+
`delete_telegram_message`, `list_telegram_chats`) and Google Business Profile (below). `schedule_post` / `list_scheduled` / `cancel_scheduled` give
|
|
281
281
|
you one content calendar over exactly that set. `upload_file` brings in any external or local media, not just
|
|
282
282
|
Hermoso renders.
|
|
283
283
|
*X posting bills credits per API call (X charges per request); a post containing a link costs 13× one without.*
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
**Google Business Profile** is the panel a business shows on Google Search and Maps. `post_to_google_business`
|
|
285
|
+
publishes a Post with text, optionally one photo, and a call-to-action button (Google takes no video in Posts, so
|
|
286
|
+
a schedule with only a video is refused for that channel); `list_google_business_posts` and
|
|
287
|
+
`delete_google_business_post` manage what is showing. `list_google_business_reviews` finds the reviews with no
|
|
288
|
+
answer yet and `reply_to_google_business_review` answers one as the business. `google_business_insights` reports
|
|
289
|
+
search and Maps impressions, calls, website clicks and direction requests, and `google_business_search_keywords`
|
|
290
|
+
the words people searched to find the listing. `list_business_locations`, `get_business_location` and
|
|
291
|
+
`update_business_location` read and edit the listing itself: hours, phone, website, description, categories.
|
|
292
|
+
Business Profile Q&A is not offered: Google retired it on 2025-11-03.
|
|
293
|
+
|
|
294
|
+
**Message customers on WhatsApp** — messaging, not a twelfth publishing channel: you message a person, and
|
|
289
295
|
nothing here posts to a feed. `list_whatsapp_accounts` finds the Business Account and its
|
|
290
296
|
numbers, `list_whatsapp_templates` / `create_whatsapp_template` / `delete_whatsapp_template` manage the templates
|
|
291
297
|
Meta reviews, and `send_whatsapp_message` sends one — confirm-gated, because it reaches a real phone and Meta
|
|
@@ -1256,7 +1256,7 @@ export const neutralNote = (t) => {
|
|
|
1256
1256
|
// sweeps and drift watches queued for later (measured on a fresh local account: one `agentidle` "nudge", queued for
|
|
1257
1257
|
// tomorrow, would have shown as "1 render in progress"). Mirrors server.js JOB_TYPES_HIDDEN_FROM_V1, and the check
|
|
1258
1258
|
// fails if that list gains a type this one lacks. A job queued for LATER is not in progress either.
|
|
1259
|
-
export const PANEL_HIDDEN_JOB_TYPES = Object.freeze(['purge', 'agentidle', 'agentsetupmail', 'uploadretention', 'connectordrift', 'falwatch', 'faceassetsweep', 'searchvis', 'mcpcanary', 'welcomeads', 'lifecycleedu', 'connhealth', 'chargednothing', 'emailfeedback']);
|
|
1259
|
+
export const PANEL_HIDDEN_JOB_TYPES = Object.freeze(['purge', 'agentidle', 'agentsetupmail', 'uploadretention', 'miniappretention', 'connectordrift', 'falwatch', 'faceassetsweep', 'searchvis', 'mcpcanary', 'welcomeads', 'lifecycleedu', 'connhealth', 'chargednothing', 'emailfeedback']);
|
|
1260
1260
|
const ageText = (h) => (h == null || !Number.isFinite(+h) ? '' : +h < 1 ? 'just now' : +h < 48 ? `${Math.round(+h)}h ago` : `${Math.round(+h / 24)}d ago`);
|
|
1261
1261
|
const VIDEO_RE = /\.(mp4|webm|mov|m4v)([?#]|$)/i;
|
|
1262
1262
|
const clip = (s, n) => { const t = String(s ?? '').replace(/\s+/g, ' ').trim(); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
|
package/mcp/roster-scope.mjs
CHANGED
|
@@ -95,6 +95,8 @@ export const TOOL_PROVIDER_RULES = [
|
|
|
95
95
|
// collide with Hermoso billing, which is buy_credits / billing_status / upgrade_plan and never says "stripe".
|
|
96
96
|
[/stripe/, 'stripe'],
|
|
97
97
|
[/hubspot/, 'hubspot'],
|
|
98
|
+
// Intercom (2026-10-12): the brand's OWN support inbox, offered only to a workspace that connected it.
|
|
99
|
+
[/intercom/, 'intercom'],
|
|
98
100
|
// ── posting-only channels ──
|
|
99
101
|
[/youtube/, 'youtube'],
|
|
100
102
|
[/google_business|business_location|^list_business_(categories|attributes)$|^business_google_updated$/, 'google_business'],
|
package/mcp/tools.mjs
CHANGED
|
@@ -378,6 +378,7 @@ export const MCP_INSTRUCTIONS_WIDGET = [
|
|
|
378
378
|
'• WORKSPACE: list_brands, use_brand, create_brand, hermoso_credits, report_bug, request_feature.',
|
|
379
379
|
"CREDITS: tools that run an AI model or ad research use credits from the user's Hermoso account, and the reply says how many. If the balance is too low, say so plainly; the user manages their account at hermoso.ai. Never start a purchase.",
|
|
380
380
|
'CONFIRM with the user before scheduling a post, deleting anything, or replacing a saved profile (draft_brand with save:true).',
|
|
381
|
+
'SHOWING RESULTS: the widget already shows each finished image and video. Never repeat one as a markdown image or an image link (the chat cannot load it and shows a broken tile); describe it in words and give the link only if the user asks for it.',
|
|
381
382
|
].join('\n');
|
|
382
383
|
// `opts.widgetHost` is the transport's own host decision (mcp/http.mjs isWidgetHost), the same one registerTools gets.
|
|
383
384
|
export const instructionsFor = (scope, opts = {}) => (scope && scope.directory === 'scoped' ? MCP_INSTRUCTIONS_DIRECTORY : (opts && opts.widgetHost ? MCP_INSTRUCTIONS_WIDGET : MCP_INSTRUCTIONS));
|
|
@@ -17870,7 +17871,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17870
17871
|
brand: z.union([z.string(), z.object({}).passthrough()]).optional().describe('brand name, or a brand profile object {name,domain,category,palette,products,…}. OMIT to use the workspace’s SAVED brand + memory automatically (see get_brand); use draft_brand to onboard a new one'),
|
|
17871
17872
|
product: z.string().describe('what to advertise + any angle/offer the user specified'),
|
|
17872
17873
|
format: z.enum(['auto', 'image', 'video']).optional().describe("'image', 'video', or 'auto' when unspecified"),
|
|
17873
|
-
durationSeconds: z.number().optional().describe('VIDEO ONLY — the total spot length the user explicitly asked for, in seconds, copied verbatim (30 for "a 30 second ad"). The planner authors the storyboard TO it: the scenes’ seconds sum to it and the script is word-budgeted for it. Supported range 4–180; anything outside is CLAMPED to it (the reply says so). A length that fits ONE clip of the render model renders as a single continuous pass; anything longer is STITCHED from acts filled to that model’s clip maximum with the remainder last (on a 15s-clip model, 40 -> 15+15+10 and 17 -> 13+4) — never time-compressed. The maximum is the model’s own: 15s on most, 30s on the longest-clip model, so a 30s ad can be one unbroken take rather than two acts. Omit when the user named no length; do NOT pass a guess: an omitted value is the default, a
|
|
17874
|
+
durationSeconds: z.number().optional().describe('VIDEO ONLY — the total spot length the user explicitly asked for, in seconds, copied verbatim (30 for "a 30 second ad"). The planner authors the storyboard TO it: the scenes’ seconds sum to it and the script is word-budgeted for it. Supported range 4–180; anything outside is CLAMPED to it (the reply says so). A length that fits ONE clip of the render model renders as a single continuous pass; anything longer is STITCHED from acts filled to that model’s clip maximum with the remainder last (on a 15s-clip model, 40 -> 15+15+10 and 17 -> 13+4) — never time-compressed. The maximum is the model’s own: 15s on most, 30s on the longest-clip model, so a 30s ad can be one unbroken take rather than two acts. Omit when the user named no length; do NOT pass a guess: an omitted value is the default, a 15s spot in one take, or 30s for a two-person format such as a street interview (2026-10-14), and a length the user names always wins.'),
|
|
17874
17875
|
hook: z.string().optional().describe('force the VISUAL scroll-stop mechanic the opening beat is built on — a hook id from list_hooks (e.g. "direct_callout", "mid_problem", "macro_asmr"). Omit to let the planner pick. A hook that cannot be delivered in this brief is DROPPED with the reason rather than rendered wrongly — an on-screen-text hook on an authentic/UGC ad is the one that bites, because that register carries zero on-screen text.'),
|
|
17875
17876
|
setting: z.string().optional().describe('force the WHERE — a setting id from list_hooks (e.g. "kitchen", "gym", or a surreal one like "volcano_rim" / "airplane_wing", which are played 100% straight and never acknowledged). Omit for a neutral setting.'),
|
|
17876
17877
|
recipe: z.string().optional().describe('a recipe id from hermoso_capabilities to force an archetype'),
|
|
@@ -18042,13 +18043,17 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
18042
18043
|
// how to fix it. Nothing was re-rendered; the image is still delivered so the caller can judge it.
|
|
18043
18044
|
const _imgHead = (pc) => (pc && (pc.verdict === 'mismatch' || pc.verdict === 'absent')) ? '⚠ Image delivered, but the product in it is NOT your product — do not use it as is (details below): ' : 'Image ready: ';
|
|
18044
18045
|
const _wctx = mcpCtx.getStore();
|
|
18045
|
-
|
|
18046
|
+
// THE LOCAL (stdio / CLI) TRANSPORT QUEUES TOO (2026-10-15). Its one long HTTPS request to /api/generate/image went
|
|
18047
|
+
// through the public edge, which answers 524 at 100s while the render finishes server-side: a slow provider turned a
|
|
18048
|
+
// delivered image into an error. No request context = the local transport; it now queues the same job and polls it
|
|
18049
|
+
// (awaitRenderJob: up to 10 minutes locally), so no single request outlives the edge.
|
|
18050
|
+
if (!_wctx || (_wctx.waitMs !== undefined && _wctx.waitMs !== null)) {
|
|
18046
18051
|
const q = await apiPost('/api/generate/image', { ..._imgBody, queue: true });
|
|
18047
18052
|
const r = await awaitRenderJob({ id: q.jobId });
|
|
18048
18053
|
if (r.stillRendering) return { content: [{ type: 'text', text: `Image queued as job ${r.jobId}. It is rendering; read it with get_job.${q.productNote ? `\n${q.productNote}` : ''}` }], structuredContent: { jobId: r.jobId, stillRendering: true, ...(q.productNote ? { productNote: q.productNote } : {}), ...(q.brandPhoto ? { brandPhoto: q.brandPhoto } : {}) } };
|
|
18049
18054
|
const img = await imageBlock(r.url);
|
|
18050
18055
|
const _qNote = [q.productNote, r.raw?.productNote].filter(Boolean).join(' '); // the route's note (logo, lineup) + the worker's (crop, the product check)
|
|
18051
|
-
return { content: [{ type: 'text', text: `${_imgHead(r.raw?.productCheck)}${r.url}${r.model ? ` (${r.model})` : ''}${_qNote ? `\n${_qNote}` : ''}` }, ...(img ? [img] : [])], structuredContent: { image: r.url, model: r.model, jobId: r.jobId, creditsUsed: r.creditsUsed, ...(_qNote ? { productNote: _qNote } : {}), ...(r.raw?.productCheck ? { productCheck: r.raw.productCheck } : {}), ...(r.raw?.labelPass ? { labelPass: r.raw.labelPass } : {}), ...(q.brandPhoto ? { brandPhoto: q.brandPhoto } : {}) } };
|
|
18056
|
+
return { content: [{ type: 'text', text: `${_imgHead(r.raw?.productCheck)}${r.url}${r.model ? ` (${r.model})` : ''}${switchNote({ raw: r.raw || {} })}${_qNote ? `\n${_qNote}` : ''}` }, ...(img ? [img] : [])], structuredContent: { image: r.url, model: r.model, jobId: r.jobId, creditsUsed: r.creditsUsed, ...(_qNote ? { productNote: _qNote } : {}), ...(r.raw?.productCheck ? { productCheck: r.raw.productCheck } : {}), ...(r.raw?.labelPass ? { labelPass: r.raw.labelPass } : {}), ...(r.raw?.logoCheck ? { logoCheck: r.raw.logoCheck } : {}), ...(q.brandPhoto ? { brandPhoto: q.brandPhoto } : {}) } };
|
|
18052
18057
|
}
|
|
18053
18058
|
const d = await apiPost('/api/generate/image', _imgBody); // explicit boolean so the server's saved-brand hydration default is unambiguous
|
|
18054
18059
|
const img = await imageBlock(abs(d.image)); // show the actual creative inline in Claude, not just a URL
|
|
@@ -18795,8 +18800,11 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
18795
18800
|
}, wrap(async (a) => {
|
|
18796
18801
|
// A PROMPT OR SHOTS, EITHER ONE (2026-10-05). `shots` replaces `prompt` (Kling sends multi_prompt and no prompt at all),
|
|
18797
18802
|
// so the schema leaves prompt optional and the one refusal lives here: neither is nothing to render. Free, no network.
|
|
18803
|
+
// `speak` alone is a whole request too (2026-10-08): the talk-to-camera lane writes its own staging when prompt is
|
|
18804
|
+
// empty (lib/talking-video.mjs talkingVideoPrompt), and this refusal had been turning every speak-only call away.
|
|
18798
18805
|
const _hasShots = Array.isArray(a.shots) && a.shots.length > 0;
|
|
18799
|
-
|
|
18806
|
+
const _hasSpeak = !!String(a.speak || '').trim();
|
|
18807
|
+
if (!String(a.prompt || '').trim() && !_hasShots && !_hasSpeak) return ok('NOTHING WAS RENDERED and nothing was charged. Say what the clip should show: send `prompt`, or `shots` (a list of {prompt, seconds}) for a multi-shot clip on a model with multiShot true in hermoso_capabilities.', { refused: 'missing_prompt' });
|
|
18800
18808
|
// NEVER SILENTLY TRUNCATE A LENGTH ASK. One generation = ONE continuous clip, and workVideo resolves an
|
|
18801
18809
|
// over-long ask by picking the longest clip the model has — so a 40s generate_video call used to render 15s and
|
|
18802
18810
|
// report success. A refusal that names the real ceiling and the real alternative is the honest answer, and it
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.408",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
|
-
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 906 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
|
|
5
|
+
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 906 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, Telegram 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"
|