hermoso 0.1.471 → 0.1.472
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 +11 -5
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -4800,8 +4800,14 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
4800
4800
|
// binds both, so the human is told exactly what they pay and what they get.
|
|
4801
4801
|
const packs = (cfg.packs || []).map(p => ({ id: p.id, credits: p.credits, priceUsd: p.priceUsd }));
|
|
4802
4802
|
const priceText = (p) => `$${p.priceUsd}`;
|
|
4803
|
+
// THE PACK-VS-PLAN COMPARISON (2026-10-30): an account with no monthly plan is told, on the pack it is about to buy, the
|
|
4804
|
+
// plan that beats it and by how much, measured by the server (packs[].planCompare, lib/pack-plan-compare.mjs). Not on a
|
|
4805
|
+
// host that renders widgets, the same rule as hermoso_credits' upsell line.
|
|
4806
|
+
const _cmp = new Map((cfg.packs || []).filter(p => p.planCompare).map(p => [p.id, p.planCompare]));
|
|
4807
|
+
const cmpOf = (id) => (hostRendersWidgets() ? null : _cmp.get(id) || null);
|
|
4808
|
+
const cmpLine = (c) => c ? `\n\nBefore paying: ${c.line} A monthly plan is the better buy here; upgrade_plan moves the account to ${c.planName} (a person pays on Stripe).` : '';
|
|
4803
4809
|
if (!pack) {
|
|
4804
|
-
const lines = packs.map(p => `• ${p.id} — ${p.credits.toLocaleString()} credits · ${priceText(p)}`).join('\n') || '(no packs configured)';
|
|
4810
|
+
const lines = packs.map(p => `• ${p.id} — ${p.credits.toLocaleString()} credits · ${priceText(p)}${cmpOf(p.id) ? ` (${cmpOf(p.id).headline})` : ''}`).join('\n') || '(no packs configured)';
|
|
4805
4811
|
let _up = null; try { _up = (await apiGet('/api/billing/status'))?.planUpsell || null; } catch {}
|
|
4806
4812
|
return ok(`Credit packs you can buy:\n${lines}\n\nCall buy_credits again with pack="<id>". With a saved card it's a one-click charge (you'll be asked to confirm); otherwise you get a checkout link for your human.${_up ? `\n\n${_up.line} ${_up.note} Top-up credits use standard prices; upgrade_plan lists the monthly plans.` : ''}\n\nTip: to never run out again, turn on auto-reload (set_auto_reload) — it auto-tops-up from the saved card the moment your balance runs low.`, { packs });
|
|
4807
4813
|
}
|
|
@@ -4822,7 +4828,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
4822
4828
|
const mintQuote = () => {
|
|
4823
4829
|
const t = `qt1|${match.id}|${match.priceUsd}|${Math.floor(Date.now() / 1000) + 600}|${(globalThis.crypto?.randomUUID?.() || String(Date.now())).slice(0, 8)}`;
|
|
4824
4830
|
_mintedQuotes.add(t); if (_mintedQuotes.size > 50) _mintedQuotes.delete(_mintedQuotes.values().next().value); // bounded
|
|
4825
|
-
return ok(`Ready to charge ${card} ${priceText(match)} for ${match.credits.toLocaleString()} credits (one-click, no redirect — same as the app's Add credits button). Confirm with your human if they haven't already asked for this, then call buy_credits again with pack="${match.id}", confirm:true and quote_token="${t}" (valid 10 minutes)
|
|
4831
|
+
return ok(`Ready to charge ${card} ${priceText(match)} for ${match.credits.toLocaleString()} credits (one-click, no redirect — same as the app's Add credits button). Confirm with your human if they haven't already asked for this, then call buy_credits again with pack="${match.id}", confirm:true and quote_token="${t}" (valid 10 minutes).${cmpLine(cmpOf(match.id))}`, { quote: { packId: match.id, credits: match.credits, priceUsd: match.priceUsd, card: st.card || null, quoteToken: t, expiresInSeconds: 600 } });
|
|
4826
4832
|
};
|
|
4827
4833
|
if (!confirm || !quote_token) return mintQuote();
|
|
4828
4834
|
const qp = String(quote_token).split('|');
|
|
@@ -4836,7 +4842,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
4836
4842
|
return ok(`Done — charged ${card} ${priceText(match)}; ${match.credits.toLocaleString()} credits are on the account now. (Receipt lands in Billing → invoice history.)`, d);
|
|
4837
4843
|
}
|
|
4838
4844
|
const d = await apiPost('/api/billing/checkout-link', { packId: pack });
|
|
4839
|
-
return ok(`Checkout link for ${Number(d.credits ?? match.credits).toLocaleString()} credits ($${d.amountUsd ?? match.priceUsd}):\n${d.url}\n\nGive this URL to your human to pay on Stripe's secure page — credits post automatically once payment completes, and their card saves for one-click top-ups (in-app AND via this tool) from then on. Nothing is charged until they pay
|
|
4845
|
+
return ok(`Checkout link for ${Number(d.credits ?? match.credits).toLocaleString()} credits ($${d.amountUsd ?? match.priceUsd}):\n${d.url}\n\nGive this URL to your human to pay on Stripe's secure page — credits post automatically once payment completes, and their card saves for one-click top-ups (in-app AND via this tool) from then on. Nothing is charged until they pay.${cmpLine(hostRendersWidgets() ? null : d.planCompare || cmpOf(match.id))}`, d);
|
|
4840
4846
|
}));
|
|
4841
4847
|
|
|
4842
4848
|
// BILLING SURFACE (read → top-up → plan/auto-reload): hermoso_credits (balance) → buy_credits (top-up link) →
|
|
@@ -13630,7 +13636,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
13630
13636
|
targetUrl: z.string().describe('the landing page (must not block OAI-AdsBot / OAI-SearchBot in robots.txt)'),
|
|
13631
13637
|
imageUrl: z.string().optional().describe('public https URL of a STILL image — every text ad needs imageUrl OR fileId (ChatGPT Ads refuses an ad with no creative.file_id unless the campaign is a product feed; measured 2026-09-06); a video URL is refused, this channel has no video format'),
|
|
13632
13638
|
fileId: z.string().optional().describe('reuse an image ChatGPT Ads already holds: the fileId list_openai_ads_campaigns returns for an existing ad (list with adGroupId). Wins over imageUrl; use it to copy an ad exactly'),
|
|
13633
|
-
imageUrls: z.array(z.string()).optional().describe('4–6 public https URLs of
|
|
13639
|
+
imageUrls: z.array(z.string()).optional().describe('4–6 public https URLs of EXACTLY square (1:1) stills, in display order, for an ad with MULTIPLE images (one title, body and landing page shared by all). Each URL is measured before anything is uploaded: OpenAI refuses an image even 1 pixel off square, and a set of 16:9 images. OpenAI enables this per ad account, and with automatic bidding only if the account is also enabled for that — otherwise use a fixed bid. Not with imageUrl/fileId or price'),
|
|
13634
13640
|
fileIds: z.array(z.string()).optional().describe('the same as imageUrls with images already on the account (4–6 file ids); may be mixed with imageUrls, ids first. On an edit, omit both to keep the ad\u2019s current images; a new list replaces them all'),
|
|
13635
13641
|
price: z.string().optional().describe('optional price string shown on the card'),
|
|
13636
13642
|
});
|
|
@@ -15113,7 +15119,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
15113
15119
|
}));
|
|
15114
15120
|
server.registerTool('create_openai_ads_ad', {
|
|
15115
15121
|
title: 'Create a ChatGPT Ads ad',
|
|
15116
|
-
description: 'Create the actual AD inside a ChatGPT Ads ad group — the object that carries the creative; a campaign or ad group alone shows nothing at all. The creative is a TEXT + IMAGE CARD: a title of 3–50 characters, body copy of 100 characters maximum, one landing page URL and one still image. THERE IS NO VIDEO FORMAT ON THIS PLATFORM — a video URL is refused outright, so never offer one. Created paused; creating it active inside a live ad group starts REAL AD SPEND, so that needs confirm:true. OpenAI REVIEWS every ad (usually a few minutes) and the ad is read back with its review status: until that says approved the ad CANNOT show, so report the review status rather than calling it live. The landing page is also checked against robots.txt for OAI-AdsBot / OAI-SearchBot blocks — a page that blocks those agents cannot run ChatGPT ads at all, and the note says so.',
|
|
15122
|
+
description: 'Create the actual AD inside a ChatGPT Ads ad group — the object that carries the creative; a campaign or ad group alone shows nothing at all. The creative is a TEXT + IMAGE CARD: a title of 3–50 characters, body copy of 100 characters maximum, one landing page URL and one still image. THERE IS NO VIDEO FORMAT ON THIS PLATFORM — a video URL is refused outright, so never offer one. Created paused; creating it active inside a live ad group starts REAL AD SPEND, so that needs confirm:true. OpenAI REVIEWS every ad (usually a few minutes) and the ad is read back with its review status: until that says approved the ad CANNOT show, so report the review status rather than calling it live. The landing page is also checked against robots.txt for OAI-AdsBot / OAI-SearchBot blocks — a page that blocks those agents cannot run ChatGPT ads at all, and the note says so. OpenAI creative enhancements (on by default for self-serve campaigns since 2026-10-30) may serve this copy rewritten, translated or on another page of the same site; the switch is in ChatGPT Ads Manager (account and campaign) and the API does not expose it, so never promise the exact words will run.',
|
|
15117
15123
|
inputSchema: {
|
|
15118
15124
|
adGroupId: z.string().describe('ad group id to create the ad in'), name: z.string().optional().describe('internal name — defaults to the title'),
|
|
15119
15125
|
creative: oaiCreativeShape.describe('title 3-50 characters, body up to 100, landing page URL and one still image; no video'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.472",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 1089 tools. No markup on models. Publishing, scheduling, ad campaign management, comments, DMs and analytics are free on every plan. 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",
|