hermoso 0.1.528 → 0.1.530

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 CHANGED
@@ -67,7 +67,7 @@ Ask in your own words. The agent picks the tools:
67
67
  - "Compare last week's Google Ads and Meta Ads results and tell me what to pause."
68
68
  - "Schedule this video to TikTok, Instagram Reels and YouTube Shorts for Tuesday at 9am."
69
69
 
70
- **1178 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
70
+ **1195 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
71
71
  catalog with exact per-render credit costs plus the full capability map.
72
72
 
73
73
  **No markup on models** on Starter, Pro and Studio. Stop overpaying markups on AI models. Plans: [hermoso.ai/pricing](https://hermoso.ai/pricing).
@@ -78,7 +78,7 @@ website.
78
78
 
79
79
  **Most of it costs nothing.** Publishing and scheduling posts, building and managing paid campaigns, analytics
80
80
  and insights, comments and DMs, connectors, profiles and team seats cost **no credits on any plan**, with no
81
- per-post fee and no seat pricing. The Free plan includes 10 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($9.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits. Credits are spent only on running an AI model (image, video,
81
+ per-post fee and no seat pricing. The Free plan includes 5 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($19.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 160 free credits as the Free plan, and monthly credits start on Starter. Credits are spent only on running an AI model (image, video,
82
82
  voice, text, planning, post-production) and on Ad Spy research, and posting an ad you already rendered is never a
83
83
  second charge. The one exception is X (Twitter), where posting and reads bill credits per call because X
84
84
  charges per API request (`hermoso_credits` prints the exact credits per X post).
@@ -102,8 +102,8 @@ just download it. Use the one piece you need, or all of it together.
102
102
  ## Install in one command
103
103
 
104
104
  This repo is a plugin marketplace, a Gemini CLI extension and a skills package at once, so a coding agent takes
105
- Hermoso in one line. Every install brings the same five skills (`hermoso-research`, `hermoso-generate`,
106
- `hermoso-ad-from-brand`, `hermoso-product-photoshoot`, `hermoso-marketing`), and the skills drive the `hermoso`
105
+ Hermoso in one line. Every install brings the same six skills (`hermoso-research`, `hermoso-generate`,
106
+ `hermoso-ad-from-brand`, `hermoso-product-photoshoot`, `hermoso-marketing`, `hermoso-edit-timeline`), and the skills drive the `hermoso`
107
107
  CLI through `npx`. No
108
108
  tool list is loaded into your session: a CLI command costs nothing until it runs, and it reaches every tool.
109
109
  The first time, your agent runs `npx -y hermoso auth login`, which opens a browser to sign in.
@@ -252,7 +252,7 @@ Connect, approve with your Hermoso account, and you are done: the full toolset w
252
252
  1. **Get an account** at [app.hermoso.ai](https://app.hermoso.ai). The free tier is included; plans and credits
253
253
  are the same ones the web Studio uses. Or skip the browser entirely and let your agent sign itself up on a paid
254
254
  plan with `POST /v1/signup` (above).
255
- 2. **Install the plugin.** It adds the five Hermoso skills, which drive the `hermoso` CLI through `npx`:
255
+ 2. **Install the plugin.** It adds the six Hermoso skills, which drive the `hermoso` CLI through `npx`:
256
256
 
257
257
  ```bash
258
258
  claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso
@@ -276,7 +276,7 @@ your browser (`--no-browser` prints the link on a headless machine). Measured ag
276
276
 
277
277
  Your agent now has the full studio **with your profile's context**: the brand details, products, logos and
278
278
  learned memory you set up in the web app apply automatically (`get_brand` shows what's saved; omit `brand` in
279
- `plan_ad`/`plan_variations` to use it). Renders bill your Hermoso credits, at the same prices as the Studio. Only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management and analytics cost no credits on any plan (posting to X and reading X data are the one per-call exception, managing X ads is free). The Free plan includes 10 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($9.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 250+ free credits as the Free plan, monthly credits start on Starter, and Autopilot spends credits.
279
+ `plan_ad`/`plan_variations` to use it). Renders bill your Hermoso credits, at the same prices as the Studio. Only AI model runs and Ad Spy research spend credits; publishing, scheduling, ads management and analytics cost no credits on any plan (posting to X and reading X data are the one per-call exception, managing X ads is free). The Free plan includes 5 posts a month on up to 3 platforms; every paid plan posts without a limit and runs autopilot. Basic ($19.99 a month) is unlimited posting and Autopilot with no monthly credits: it includes the same 160 free credits as the Free plan, and monthly credits start on Starter.
280
280
 
281
281
  ## 1. MCP server (stdio), optional in a coding agent
282
282
 
@@ -304,7 +304,7 @@ block entirely if you signed in above; it is there for CI, where the process can
304
304
 
305
305
  Then ask your agent: *“Generate an image ad with Hermoso.”*
306
306
 
307
- ### What the 1178 tools cover
307
+ ### What the 1195 tools cover
308
308
 
309
309
  **Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
310
310
  Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
@@ -480,7 +480,7 @@ the package, so they need no key, no network and no sign-in.
480
480
 
481
481
  ## 3. Skills: what a coding agent installs
482
482
 
483
- `skills/` holds five installable skills:
483
+ `skills/` holds six installable skills:
484
484
 
485
485
  | Skill | What it does |
486
486
  | --- | --- |
@@ -489,6 +489,7 @@ the package, so they need no key, no network and no sign-in.
489
489
  | `hermoso-ad-from-brand` | A brand or domain to one on-brand ad, concept and copy included |
490
490
  | `hermoso-product-photoshoot` | A real product photo composited into studio, lifestyle or hero scenes |
491
491
  | `hermoso-marketing` | The whole loop: research, create, publish and schedule, paid campaigns, measure |
492
+ | `hermoso-edit-timeline` | The manual for composing any video edit or transition with `edit_timeline` |
492
493
 
493
494
  They are what every one-command install at the top of this page adds, in Claude Code, Codex, Gemini CLI and
494
495
  through `npx skills add`. From a clone, copying works too:
@@ -24,11 +24,11 @@ export const CHATGPT_ROSTER_FAMILIES = Object.freeze([
24
24
  'bluesky', // publishing
25
25
  'openai_ads', // big-platform ads
26
26
  'reddit_ads', // long tail
27
- 'bing_webmaster', // analytics
28
27
  ]);
29
28
  // Left out to stay under the cap, in rank order (the first one is the next to get in when room appears).
30
29
  export const CHATGPT_ROSTER_LEFT_OUT = Object.freeze([
31
30
  'x_ads', // long tail
31
+ 'bing_webmaster', // analytics
32
32
  'mixpanel', // analytics
33
33
  'apple_ads', // long tail
34
34
  'pinterest_ads', // long tail
@@ -42,6 +42,7 @@ export const CHATGPT_ROSTER_LEFT_OUT = Object.freeze([
42
42
  'mailchimp', // analytics
43
43
  'posthog', // analytics
44
44
  'stripe', // analytics
45
+ 'amazon_ads', // long tail
45
46
  'applovin_ads', // long tail
46
47
  'canva', // long tail
47
48
  'notion', // long tail
package/mcp/client.mjs CHANGED
@@ -460,7 +460,10 @@ export async function connectedProviders() {
460
460
  const shopifyStore = r?.shopifyStore === true ? true : r?.shopifyStore === false ? false : null;
461
461
  // flagsOn (2026-11-03): tools that ship dark and are switched ON on this server (roster-scope.mjs FLAGGED_OFF_TOOLS).
462
462
  const flagsOn = new Set((Array.isArray(r?.flagsOn) ? r.flagsOn : []).filter((n) => typeof n === 'string' && n));
463
- return { connected: new Set(list.filter((p) => typeof p === 'string' && p)), readOk: true, offered, gated, connectLink, withheld, flagsOn, shopifyStore };
463
+ // brand (2026-11-08): which brand this connection resolved to, so a "not connected" refusal can name it and the
464
+ // switch. Absent on an older server or a session login; never invented.
465
+ const brand = r?.brand && typeof r.brand === 'object' && typeof r.brand.profileId === 'string' ? { profileId: r.brand.profileId, name: typeof r.brand.name === 'string' ? r.brand.name : null, shared: !!r.brand.shared, source: String(r.brand.source || '') } : null;
466
+ return { connected: new Set(list.filter((p) => typeof p === 'string' && p)), readOk: true, offered, gated, connectLink, withheld, flagsOn, shopifyStore, brand };
464
467
  } catch { return { connected: new Set(), readOk: false }; }
465
468
  }
466
469
  // Upload raw file BYTES to /api/upload (150MB, persists → returns {url,kind,bytes}). Overrides the JSON content-type so
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "flags": {
3
+ "AMAZON_ADS_API_APPROVED": false,
3
4
  "CANVA_APP_APPROVED": false,
4
5
  "WHOP_API_KEY": true
5
6
  }
@@ -74,6 +74,7 @@ export const TOOL_PROVIDER_RULES = [
74
74
  [/_microsoft_ads_|^microsoft_ads_|_microsoft_merchant_/, 'microsoft_ads'],
75
75
  [/_apple_ads_|^apple_ads_/, 'apple_ads'],
76
76
  [/_openai_ads_|^openai_ads_/, 'openai_ads'],
77
+ [/_amazon_ads_|^amazon_ads_/, 'amazon_ads'], // Amazon Ads (2026-11-07): Sponsored Products through Login with Amazon
77
78
  [/applovin/, 'applovin_ads'], // AppLovin Ads (2026-10-04) has no posting sibling, so the bare name is unambiguous
78
79
  // Google Ads owns Merchant Center + the Ads↔Analytics link (both are Google Ads API surfaces, not GA4 ones).
79
80
  [/_google_ads_|^google_ads_|_merchant_|^merchant_|^list_merchant_|link_google_ads_to_analytics/, 'google_ads'],
@@ -255,13 +256,18 @@ export const WHOP_DARK_TOOLS = Object.freeze(['post_to_whop', 'list_whop_destina
255
256
  // Whop they are lit PER CALLER: server.js flagsOnNow(connected, req) names them in `flagsOn` for a workspace that already
256
257
  // holds a Canva connection (ours and Canva's reviewer's), so an existing connection keeps its tools while the gate is shut.
257
258
  export const CANVA_DARK_TOOLS = Object.freeze(['save_to_canva', 'list_canva_designs', 'export_canva_design', 'list_canva_brand_templates', 'autofill_canva_template', 'list_canva_folder', 'create_canva_folder']);
258
- export const FLAGGED_OFF_TOOLS = new Set(['reangle_video', ...WHOP_DARK_TOOLS, ...CANVA_DARK_TOOLS]);
259
+ // THE AMAZON ADS TOOLS SHIP DARK WITH AMAZON'S API-ACCESS APPROVAL (2026-11-07). The connector is built, but Amazon has not
260
+ // approved Hermoso's Amazon Ads API access, so CONNECTOR_GATES.amazon_ads is shut (AMAZON_ADS_API_APPROVED unset) and these
261
+ // are no capability for a customer yet: not counted, not documented, not on a roster. Lit PER CALLER exactly like Canva:
262
+ // server.js flagsOnNow names them for a workspace already holding an Amazon Ads connection and for the gate's team pass.
263
+ export const AMAZON_ADS_DARK_TOOLS = Object.freeze(['list_amazon_ads_profiles', 'list_amazon_ads_campaigns', 'create_amazon_ads_campaign', 'create_amazon_ads_ad_group', 'add_amazon_ads_keywords', 'create_amazon_ads_product_ads', 'set_amazon_ads_status', 'set_amazon_ads_budget', 'update_amazon_ads_object', 'delete_amazon_ads_object', 'amazon_ads_report']);
264
+ export const FLAGGED_OFF_TOOLS = new Set(['reangle_video', ...WHOP_DARK_TOOLS, ...CANVA_DARK_TOOLS, ...AMAZON_ADS_DARK_TOOLS]);
259
265
  // THE PRODUCTION FLAG THAT LIFTS A DARK TOOL (2026-11-03, owner: count and document the Whop tools whenever their gate
260
266
  // is open in prod, keep the automatic gate). A tool named here is dark only while that CONNECTOR_GATES flag is off in
261
267
  // production, as recorded in docs-site/prod-gate-flags.json, the file the docs build already reads. The counts and the
262
268
  // docs reference read it through darkToolsUnder(); the per-caller roster still follows `flagsOn` from the server.
263
269
  // reangle_video has no entry: it follows its own REANGLE_VIDEO switch, which no production record carries.
264
- export const DARK_TOOL_GATE_FLAG = Object.freeze(Object.fromEntries([...WHOP_DARK_TOOLS.map((n) => [n, 'WHOP_API_KEY']), ...CANVA_DARK_TOOLS.map((n) => [n, 'CANVA_APP_APPROVED'])]));
270
+ export const DARK_TOOL_GATE_FLAG = Object.freeze(Object.fromEntries([...WHOP_DARK_TOOLS.map((n) => [n, 'WHOP_API_KEY']), ...CANVA_DARK_TOOLS.map((n) => [n, 'CANVA_APP_APPROVED']), ...AMAZON_ADS_DARK_TOOLS.map((n) => [n, 'AMAZON_ADS_API_APPROVED'])]));
265
271
  export function darkToolsUnder(flags) {
266
272
  const f = flags && typeof flags === 'object' ? flags : {};
267
273
  return new Set([...FLAGGED_OFF_TOOLS].filter((n) => !(DARK_TOOL_GATE_FLAG[n] && f[DARK_TOOL_GATE_FLAG[n]] === true)));
@@ -161,10 +161,10 @@ export const CREDITS_LOW_NOTE = `The Hermoso account's credit balance is too low
161
161
  const UPSELL_SENTENCE_RE = /\s*(?:please\s+)?(?:upgrade(?:\s+your\s+plan)?\s+or\s+top\s+up|top\s+up|add\s+credits|buy\s+(?:more\s+)?credits|monthly\s+plans\s+have\s+no\s+markup\s+on\s+models)\b[^.!\n]*[.!]?/gi; // the monthly-plan upsell (2026-10-20) is a promotion too: a widget host (ChatGPT) never hears it
162
162
  // ── FREE CREDITS NOT CLAIMED YET (2026-10-25, ChatGPT funnel) ─────────────────────────────────────────────────────
163
163
  // An account whose welcome credits were never added (the one-time check that a person is behind it did not pass
164
- // silently at sign-in) reads 0 credits. Its fix is opening the app once, signed in, which is not a purchase, so it is
164
+ // silently at sign-in) reads 0 credits. Its fix is opening a one-click link once (2026-11-10: signed for the account, no sign-in), which is not a purchase, so it is
165
165
  // said on every host, ChatGPT included. The server marks the refusal (meta.welcomeClaim = {credits, url}).
166
- export const welcomeClaimSentence = (w) => `This account has not received its ${Number(w?.credits) || 0} free credits yet. The user opens ${w?.url} once in a browser, signed in with the same account, and they arrive in a few seconds after a quick check that a person is there (no card).`;
167
- export const welcomeClaimHint = (w) => ({ do: `ask the user to open ${w?.url} once in a browser, signed in with the same account, to receive the ${Number(w?.credits) || 0} free credits; then run the same call again`, why: 'the account never received its free credits: they wait behind a one-time check that a person is behind the account, which runs in the browser' });
166
+ export const welcomeClaimSentence = (w) => `This account has not received its ${Number(w?.credits) || 0} free credits yet. The user opens ${w?.url} once in a browser (one click, no sign-in and no card) and they arrive in a few seconds after a quick check that a person is there.`;
167
+ export const welcomeClaimHint = (w) => ({ do: `ask the user to open ${w?.url} once in a browser (one click, no sign-in) to receive the ${Number(w?.credits) || 0} free credits; then run the same call again`, why: 'the account never received its free credits: they wait behind a one-time check that a person is behind the account, which runs in the browser' });
168
168
  export function neutralCreditsText(msg) {
169
169
  const base = String(msg ?? '').replace(UPSELL_SENTENCE_RE, '').replace(/[ \t]+$/gm, '').replace(/\s*[—–,:-]\s*$/gm, '.').trim();
170
170
  return `${base}${base ? '\n' : ''}${CREDITS_LOW_NOTE}`;