hermoso 0.1.493 → 0.1.494
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/client.mjs +1 -1
- package/mcp/tools.mjs +3 -1
- package/package.json +1 -1
package/mcp/client.mjs
CHANGED
|
@@ -159,7 +159,7 @@ async function unwrap(res, { read = false } = {}) {
|
|
|
159
159
|
// ledger with the tool name off x-hermoso-tool. wrap() reports ONLY the errors that lack this marker — a local
|
|
160
160
|
// throw, a schema rejection, a socket reset — which is what stops the twins double-counting every 4xx.
|
|
161
161
|
// `connectUrl` rides a not-connected 401 with the brand already in it, so a hint never rebuilds the link.
|
|
162
|
-
throw Object.assign(new Error(msg), { status: res.status, _viaApi: true, ...(typeof body?.code === 'string' ? { apiCode: body.code } : {}), ...(body?.connector ? { connector: body.connector } : {}), ...(typeof body?.connectUrl === 'string' ? { connectUrl: body.connectUrl } : {}), ...(body?.meta?.videoChoice && typeof body.meta.videoChoice === 'object' ? { videoChoice: body.meta.videoChoice } : {}), ...(body?.metaAuthHold === true ? { metaAuthHold: true } : {}), ...(body?.meta?.welcomeClaim && typeof body.meta.welcomeClaim === 'object' && body.meta.welcomeClaim.url ? { welcomeClaim: { credits: Number(body.meta.welcomeClaim.credits) || 0, url: String(body.meta.welcomeClaim.url) } } : {}), ...(body?.remedy && typeof body.remedy === 'object' && typeof body.remedy.code === 'string' ? { remedy: body.remedy } : {}) }); // `remedy` (2026-10-25): the server's structured next step (a publish outcome, Meta's security hold), handed on whole to wrap()'s _meta and /v1 // `videoChoice` rides a 402 for a video the caller expects and cannot afford (server videoChoiceFor) — wrap() spells its options out instead of a bare top-up line
|
|
162
|
+
throw Object.assign(new Error(msg), { status: res.status, _viaApi: true, ...(res.status === 429 ? { retryAfter: Number(body?.retryAfter) || Number(res.headers?.get?.('retry-after')) || 60 } : {}), ...(typeof body?.code === 'string' ? { apiCode: body.code } : {}), ...(body?.connector ? { connector: body.connector } : {}), ...(typeof body?.connectUrl === 'string' ? { connectUrl: body.connectUrl } : {}), ...(body?.meta?.videoChoice && typeof body.meta.videoChoice === 'object' ? { videoChoice: body.meta.videoChoice } : {}), ...(body?.metaAuthHold === true ? { metaAuthHold: true } : {}), ...(body?.meta?.welcomeClaim && typeof body.meta.welcomeClaim === 'object' && body.meta.welcomeClaim.url ? { welcomeClaim: { credits: Number(body.meta.welcomeClaim.credits) || 0, url: String(body.meta.welcomeClaim.url) } } : {}), ...(body?.remedy && typeof body.remedy === 'object' && typeof body.remedy.code === 'string' ? { remedy: body.remedy } : {}) }); // `remedy` (2026-10-25): the server's structured next step (a publish outcome, Meta's security hold), handed on whole to wrap()'s _meta and /v1 // `videoChoice` rides a 402 for a video the caller expects and cannot afford (server videoChoiceFor) — wrap() spells its options out instead of a bare top-up line
|
|
163
163
|
}
|
|
164
164
|
// WHAT THIS CALL WAS CHARGED (2026-10-19). A paid route answers {data, creditsUsed}, and the unwrap below drops the
|
|
165
165
|
// sibling, so a synchronous paid tool's reply could never say what it cost. It is added to the running tool call's
|
package/mcp/tools.mjs
CHANGED
|
@@ -596,6 +596,8 @@ const wrap = (fn) => {
|
|
|
596
596
|
// META'S SECURITY HOLD (code 31/3858385): the server's sentence already carries the steps; this names the move
|
|
597
597
|
// so an agent relays it instead of retrying or telling the user to reconnect. Read from the STRUCTURED field.
|
|
598
598
|
if (e?.metaAuthHold === true) _hints.push({ do: 'stop retrying; ask the user to clear Meta\u2019s security check: as the Facebook profile that connected Hermoso, on a computer (not the Ads Manager app), create a NEW draft ad in Ads Manager, click Start authentication (or Fix errors) on the error it shows and complete it, or follow a red banner if one is shown; then run the same call again', why: 'Meta refuses new or edited ads from that profile until it re-authenticates; the connection and permissions are fine and reconnecting with the same profile does not clear it', ...(e?.remedy?.login ? { who: e.remedy.who } : {}) });
|
|
599
|
+
// A RATE LIMIT SAYS HOW LONG, IN A NUMBER AND A SENTENCE (2026-11-02: an agent called set_youtube_thumbnail 73 times on "wait a few minutes"). Read from the structured field the client carried.
|
|
600
|
+
if (Number(e?.status) === 429 && e?.retryAfter > 0) { const _w = Number(e.retryAfter), _ww = _w < 90 ? `${Math.round(_w)} seconds` : `${Math.round(_w / 60)} minutes`; msg = `${msg} Do not call ${_tool} again yet: wait ${_ww} first. Calling it sooner is refused the same way and can make the limit last longer.`; _hints.push({ do: `wait ${_ww}, then call ${_tool} once`, why: 'the platform is rate-limiting this call, so an immediate retry cannot succeed' }); }
|
|
599
601
|
if (Number(e?.status) === 401 && e?.connector) _hints.push({ do: `have the user connect "${e.connector}" (Settings \u25b8 Connectors, or the one-click link in this message)`, why: `${e.connector} is not connected in this workspace, so this tool can only answer 401 until it is` });
|
|
600
602
|
// A PUBLISH OUTCOME IS DATA (2026-10-25, fb_679fd3573ff0): "nothing was posted" and "could not confirm" both arrive
|
|
601
603
|
// as 502, so the hint follows the server's `remedy.posted`, never the status or the prose.
|
|
@@ -614,7 +616,7 @@ const wrap = (fn) => {
|
|
|
614
616
|
// so its ambiguous-publish advice keeps the marker rather than dropping it.
|
|
615
617
|
// `remedy` (2026-10-25): the server's structured next step, whole, plus its `code` — Meta's security hold (code +
|
|
616
618
|
// steps[]) or a publish outcome (posted:false | 'unknown'). /v1 makes `code` its error code and returns `remedy`.
|
|
617
|
-
return withHints({ content: [{ type: 'text', text: msg }], isError: true, _meta: { 'hermoso.ai/error': { status: Number(e?.status) || 0, connector: e?.connector ? String(e.connector) : '', ...(e?.videoChoice?.options && typeof e.videoChoice.options === 'object' ? { options: e.videoChoice.options } : {}), ...(e?.remedy && typeof e.remedy === 'object' && typeof e.remedy.code === 'string' ? { code: e.remedy.code, remedy: e.remedy } : {}) } } }, _hints); // `options` = the video choice's three ways forward, structured, so /v1 answers its 402 with them
|
|
619
|
+
return withHints({ content: [{ type: 'text', text: msg }], isError: true, _meta: { 'hermoso.ai/error': { status: Number(e?.status) || 0, ...(Number(e?.status) === 429 && e?.retryAfter > 0 ? { retryAfterSeconds: Number(e.retryAfter) } : {}), connector: e?.connector ? String(e.connector) : '', ...(e?.videoChoice?.options && typeof e.videoChoice.options === 'object' ? { options: e.videoChoice.options } : {}), ...(e?.remedy && typeof e.remedy === 'object' && typeof e.remedy.code === 'string' ? { code: e.remedy.code, remedy: e.remedy } : {}) } } }, _hints); // `options` = the video choice's three ways forward, structured, so /v1 answers its 402 with them
|
|
618
620
|
}
|
|
619
621
|
};
|
|
620
622
|
return outer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.494",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 1108 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",
|