hermoso 0.1.112 → 0.1.113

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
@@ -5,7 +5,7 @@ scripts. Research the ads already winning in a market, generate finished image &
5
5
  composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad
6
6
  campaigns behind them — all over [MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
7
7
 
8
- **519 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
8
+ **521 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
9
9
  catalog with exact per-render credit costs plus the full capability map.
10
10
 
11
11
  **It is not all-or-nothing.** Research, creation, publishing/scheduling and ads management are four *independent*
@@ -53,7 +53,7 @@ Cursor / Codex — add to `mcp.json` (Codex uses the TOML equivalent):
53
53
 
54
54
  Then ask your agent: *“Generate an image ad with Hermoso.”*
55
55
 
56
- ### What the 519 tools cover
56
+ ### What the 521 tools cover
57
57
 
58
58
  **Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
59
59
  Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
package/mcp/tools.mjs CHANGED
@@ -3750,6 +3750,48 @@ export function registerTools(rawServer, opts = {}) {
3750
3750
  const d = await apiGet('/api/merchant/products', { merchantCenterId: a.merchantCenterId, limit: a.limit });
3751
3751
  return ok(`Merchant Center ${d.merchantCenterId}: ${d.count} product(s).\n${JSON.stringify(d.products || []).slice(0, 4000)}`, d);
3752
3752
  }));
3753
+ // ── SHOPIFY: the merchant's own storefront (2026-08-19) ──────────────────────────────────────────────────────
3754
+ // These close the LAST web-only capability in the product. `publish_to_product` existed only inside the Shopify
3755
+ // admin iframe, because its route authenticated with a ~60-second App Bridge JWT no headless caller can mint —
3756
+ // so a merchant driving Hermoso from Claude or Cursor could generate an ad FOR their product and then had to go
3757
+ // click it into the listing by hand. Web-only is the one direction the parity law calls a defect.
3758
+ //
3759
+ // There is deliberately no `shop` parameter on either tool. The store is derived server-side from the verified
3760
+ // account (a Shopify merchant's Hermoso account IS `shopify:<shop>`); accepting one from the caller would be a
3761
+ // forgeable instruction to publish into somebody else's storefront.
3762
+ server.registerTool('list_shopify_products', {
3763
+ title: 'List the Shopify catalog',
3764
+ description: "The merchant's real Shopify products — id, title, description, price, images and storefront URL. This is where the productId for publish_to_shopify_product comes from, and it doubles as ground truth about what the brand actually sells (real titles and real photos, not a guess from the website). Newest-updated first. Only works for accounts created by installing Hermoso from the Shopify App Store. Read-only, free.",
3765
+ inputSchema: {
3766
+ limit: z.number().optional().describe('how many products (1–100, default 24)'),
3767
+ cursor: z.string().optional().describe('pageInfo.endCursor from a previous call, to page further'),
3768
+ },
3769
+ outputSchema: { shop: z.string().optional(), products: z.array(z.any()).optional(), pageInfo: z.any().optional() },
3770
+ annotations: { readOnlyHint: true, openWorldHint: true },
3771
+ }, wrap(async (a) => {
3772
+ const d = await apiGet('/api/shopify/products', { limit: a.limit, cursor: a.cursor });
3773
+ const list = d.products || [];
3774
+ const lines = list.map((p) => `• ${p.title} — ${p.id}${p.price ? ` (${p.price.currency} ${p.price.min})` : ''}`).join('\n');
3775
+ return ok(`${d.shop}: ${list.length} product(s)${d.pageInfo?.hasNextPage ? ' (more available — pass cursor)' : ''}.\n${lines}`, d);
3776
+ }));
3777
+
3778
+ server.registerTool('publish_to_shopify_product', {
3779
+ title: 'Publish an image onto a Shopify product',
3780
+ description: "Attach a finished image to one of the merchant's Shopify product listings, as product media. Pass productId (from list_shopify_products — the gid://shopify/Product/… form) and a PUBLIC https imageUrl, which is what every Hermoso render returns. Shopify fetches the image server-side and processes it asynchronously, so the media can come back status PROCESSING and appear on the listing a moment later — that is success, not a failure. Only works for accounts created by installing Hermoso from the Shopify App Store. Free — the render was already paid for.",
3781
+ inputSchema: {
3782
+ productId: z.string().describe('gid://shopify/Product/… from list_shopify_products'),
3783
+ imageUrl: z.string().describe('a public https image URL — any Hermoso render URL works'),
3784
+ alt: z.string().optional().describe('alt text for accessibility and SEO; defaults to a generic credit'),
3785
+ },
3786
+ outputSchema: { shop: z.string().optional(), ok: z.boolean().optional(), productId: z.string().optional(), productUrl: z.string().optional(), media: z.any().optional() },
3787
+ }, wrap(async (a) => {
3788
+ const d = await apiPost('/api/shopify/publish-to-product', { productId: a.productId, imageUrl: a.imageUrl, alt: a.alt });
3789
+ const st = d.media?.status || 'UNKNOWN';
3790
+ return ok(`Published to ${d.productId} on ${d.shop} — media ${d.media?.id || '?'} is ${st}`
3791
+ + (st === 'PROCESSING' ? ' (Shopify is still processing it; it will appear on the listing shortly).' : '.')
3792
+ + (d.productUrl ? `\nListing: ${d.productUrl}` : ''), d);
3793
+ }));
3794
+
3753
3795
  server.registerTool('list_merchant_issues', {
3754
3796
  description: "Read the account-level issues Google reports on a Merchant Center — the answer to \"why is this product not showing?\", which Google Ads reporting CANNOT give you, because a disapproved product has no impressions to report on. Needs merchantCenterId from list_merchant_accounts. Read-only and free.",
3755
3797
  inputSchema: { merchantCenterId: z.string().describe('from list_merchant_accounts') },
@@ -7830,8 +7872,15 @@ export function registerTools(rawServer, opts = {}) {
7830
7872
  // ways a caller gets it wrong and neither is guessable: money is MICRO-currency (1,000,000 = 1 unit) and a
7831
7873
  // creative headline is capped at 34 characters.
7832
7874
  //
7833
- // NOT LIVE-VERIFIED. Every shape here comes from developers.snap.com read on 2026-08-10; no Snapchat call has
7834
- // been made from this codebase, because no SNAPCHAT_CLIENT_ID exists in the deployment yet.
7875
+ // VERIFIED-LIVE 2026-08-10 this header said the OPPOSITE until 2026-08-19 and was wrong for nine days.
7876
+ // It read "NOT LIVE-VERIFIED … no Snapchat call has been made from this codebase, because no SNAPCHAT_CLIENT_ID
7877
+ // exists in the deployment yet". The credential landed and the whole tree was swept the SAME DAY the shapes were
7878
+ // read: every Snapchat cell in tools/lib/ads-matrix.json is VERIFIED-LIVE against ad account
7879
+ // a6ef8b0b-2f33-4c41-b7e8-d756f9c987a4, and that sweep FOUND AND FIXED three defects (SNAP-1's 100%-failing
7880
+ // budget/status writes, SNAP-3, SNAP-6) — which is precisely the work a "docs only" header tells the next reader
7881
+ // has not happened. A stale not-verified claim is not harmlessly conservative: it makes someone redo a live sweep,
7882
+ // or distrust code that works. The one thing still genuinely unexercised is the chunked upload transport, and
7883
+ // that is stated where it lives (lib/snapchat-ads.mjs).
7835
7884
  server.registerTool('list_snapchat_ads_accounts', {
7836
7885
  title: 'List Snapchat organizations and ad accounts',
7837
7886
  description: 'List the Snapchat AD ACCOUNTS SHARED WITH THIS BRAND — the ones it may actually build on and spend from, which is NOT everything the Snapchat login can reach — id, name, currency, timezone and status. Every other Snapchat Ads tool needs an adAccountId and this is where it comes from. One call returns both tiers, because Snap nests ad accounts inside their organization. An account flagged as a TEST account is marked as such — those cannot serve real ads. Read-only, free.',
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.112",
3
+ "version": "0.1.113",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
- "description": "AI ad studio + marketing MCP (519 tools): build and manage ad campaigns on Meta, Google Ads, Reddit, X, TikTok, Snapchat, LinkedIn, Pinterest, Microsoft Advertising and ChatGPT Ads — generate finished video, image and UGC avatar ads, publish them to Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn and Pinterest, spy on competitor ads across the Meta, Google and LinkedIn ad libraries plus organic TikTok/Instagram/YouTube/Reddit, and read what they achieved in Google Analytics 4. CLI and Claude skills for Hermoso, the AI ad studio: brand onboarding, 30+ image/video models, finished-ad pipeline (script, voiceover, music, brand end card), ad scoring and competitor teardowns.",
5
+ "description": "AI ad studio + marketing MCP (521 tools): build and manage ad campaigns on Meta, Google Ads, Reddit, X, TikTok, Snapchat, LinkedIn, Pinterest, Microsoft Advertising and ChatGPT Ads — generate finished video, image and UGC avatar ads, publish them to Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn and Pinterest, spy on competitor ads across the Meta, Google and LinkedIn ad libraries plus organic TikTok/Instagram/YouTube/Reddit, and read what they achieved in Google Analytics 4. CLI and Claude skills for Hermoso, the AI ad studio: brand onboarding, 30+ image/video models, finished-ad pipeline (script, voiceover, music, brand end card), ad scoring and competitor teardowns.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "hermoso": "bin/hermoso.mjs"