hermoso 0.1.205 → 0.1.207
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 +2 -2
- package/mcp/tools.mjs +33 -3
- package/package.json +2 -2
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
|
-
**
|
|
8
|
+
**791 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
|
**What it connects to.** Ad platforms: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads,
|
|
@@ -171,7 +171,7 @@ block entirely if you signed in above; it is there for CI, where the process can
|
|
|
171
171
|
|
|
172
172
|
Then ask your agent: *“Generate an image ad with Hermoso.”*
|
|
173
173
|
|
|
174
|
-
### What the
|
|
174
|
+
### What the 791 tools cover
|
|
175
175
|
|
|
176
176
|
**Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
|
|
177
177
|
Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
|
package/mcp/tools.mjs
CHANGED
|
@@ -16109,7 +16109,7 @@ function memoryNoteVerdict(text) {
|
|
|
16109
16109
|
},
|
|
16110
16110
|
outputSchema: {
|
|
16111
16111
|
collections: z.array(z.any()).optional().describe('the named collections ({id, name, ads} counts)'),
|
|
16112
|
-
ads: z.array(z.any()).optional().describe('the saved ads ({key, collection, advertiser, title, body, image, video, link, platform, savedAt, tags})'),
|
|
16112
|
+
ads: z.array(z.any()).optional().describe('the saved ads ({key, collection, advertiser, title, body, image, video, link, platform, savedAt, tags}); a saved CREATOR (platform "creator") also carries `creator` — handle, platform, email, phone, link, category, followers, posts, medianPlays, engagementRate, score, profileUrl'),
|
|
16113
16113
|
total: z.number().optional().describe('how many ads the board holds in total, before any collection filter or limit'),
|
|
16114
16114
|
},
|
|
16115
16115
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
@@ -16121,7 +16121,7 @@ function memoryNoteVerdict(text) {
|
|
|
16121
16121
|
if (want && !hit) return { content: [{ type: 'text', text: `No swipefile collection called "${a.collection}". This board has: ${s.collections.map(c => c.name).join(', ') || '(none)'}.` }], isError: true };
|
|
16122
16122
|
const nameOf = (id) => (s.collections.find(c => c.id === id) || {}).name || 'My Swipefile';
|
|
16123
16123
|
const rows = s.ads.filter(x => x && (!hit || x.collectionId === hit.id));
|
|
16124
|
-
const ads = rows.slice(0, lim).map(x => ({ key: x.key, collection: nameOf(x.collectionId), advertiser: x.advertiser || '', title: x.title || '', body: String(x.body || '').slice(0, 400), image: abs(x.image || ''), video: abs(x.video || ''), link: x.link || '', platform: x.platform || '', savedAt: x.savedAt || 0, tags: x.tags || null }));
|
|
16124
|
+
const ads = rows.slice(0, lim).map(x => ({ key: x.key, collection: nameOf(x.collectionId), advertiser: x.advertiser || '', title: x.title || '', body: String(x.body || '').slice(0, 400), image: abs(x.image || ''), video: abs(x.video || ''), link: x.link || '', platform: x.platform || '', savedAt: x.savedAt || 0, tags: x.tags || null, ...(x.platform === 'creator' && x.creator ? { creator: { handle: x.creator.handle, platform: x.creator.platform, name: x.creator.name, email: x.creator.email || null, phone: x.creator.phone || null, link: x.creator.link || null, category: x.creator.category || null, followers: x.creator.followers ?? null, posts: x.creator.posts, medianPlays: x.creator.medianPlays, engagementRate: x.creator.engagementRate ?? null, score: x.creator.score, profileUrl: x.creator.profileUrl || '' } } : {}) }));
|
|
16125
16125
|
const collections = s.collections.map(c => ({ id: c.id, name: c.name, ads: s.ads.filter(x => x && x.collectionId === c.id).length }));
|
|
16126
16126
|
if (!ads.length) return ok(`The swipefile is empty${hit ? ` in "${hit.name}"` : ''}. Collections: ${collections.map(c => `${c.name} (${c.ads})`).join(', ')}.`, { collections, ads: [], total: rows.length });
|
|
16127
16127
|
const text = `${rows.length} saved ad(s)${hit ? ` in "${hit.name}"` : ''}${ads.length < rows.length ? ` — showing ${ads.length}` : ''}:\n`
|
|
@@ -16161,7 +16161,7 @@ function memoryNoteVerdict(text) {
|
|
|
16161
16161
|
const key = swipeKeyOf(it);
|
|
16162
16162
|
const ex = s.ads.find(x => x && x.key === key);
|
|
16163
16163
|
if (ex) { if (ex.collectionId !== col.id) moved++; ex.collectionId = col.id; continue; }
|
|
16164
|
-
s.ads.push({ key, collectionId: col.id, advertiser: String(it.advertiser || '').slice(0, 120), title: String(it.title || '').slice(0, 300), body: String(it.body || '').slice(0, 2000), image: String(it.image || ''), video: String(it.video || ''), link: String(it.link || it.url || ''), platform: String(it.platform || '').slice(0, 40), savedAt: Date.now() });
|
|
16164
|
+
s.ads.push({ key, collectionId: col.id, advertiser: String(it.advertiser || '').slice(0, 120), title: String(it.title || '').slice(0, 300), body: String(it.body || '').slice(0, 2000), image: String(it.image || ''), video: String(it.video || ''), link: String(it.link || it.url || ''), platform: String(it.platform || '').slice(0, 40), savedAt: Date.now() , ...(it.platform === 'creator' && it.creator && typeof it.creator === 'object' ? { creator: it.creator } : {}) });
|
|
16165
16165
|
saved++;
|
|
16166
16166
|
}
|
|
16167
16167
|
s.activeId = col.id;
|
|
@@ -16169,6 +16169,36 @@ function memoryNoteVerdict(text) {
|
|
|
16169
16169
|
return ok(`${saved} ad(s) saved${moved ? `, ${moved} moved` : ''} into “${col.name}”${created ? ' (new collection)' : ''} — it’s on the workspace swipefile now (list_swipefile to read it back).`, { ok: true, collection: col.name, collectionId: col.id, saved, moved, total: s.ads.length });
|
|
16170
16170
|
}));
|
|
16171
16171
|
|
|
16172
|
+
|
|
16173
|
+
// ── SAVED CREATORS: outreach status + notes (2026-09-05, Dave: "parity with dedicated tools"). A creator saved by
|
|
16174
|
+
// find_creators → save_to_swipefile carries `creator`; this writes the OUTREACH state onto that row so the whole
|
|
16175
|
+
// team — web and agent — sees who has been contacted, who replied, who is booked. One row, one field, never a
|
|
16176
|
+
// second store: the swipefile is already synced, tombstoned and union-merged.
|
|
16177
|
+
server.registerTool('update_saved_creator', {
|
|
16178
|
+
title: 'Update a saved creator (outreach status, note)',
|
|
16179
|
+
description: 'Set the OUTREACH STATUS and/or a NOTE on a creator already saved in the swipefile (find_creators → save_to_swipefile, or the ♥ on a creator card). Status is one of new | contacted | replied | booked | passed. The note is free text (deal terms, rate, what was sent). Reads back the updated row. Use list_swipefile to find the key. Free.',
|
|
16180
|
+
inputSchema: {
|
|
16181
|
+
key: z.string().describe("the saved row's key from list_swipefile, e.g. tiktok:handle"),
|
|
16182
|
+
status: z.enum(['new', 'contacted', 'replied', 'booked', 'passed']).optional(),
|
|
16183
|
+
note: z.string().max(2000).optional().describe('replaces the existing note; pass "" to clear it'),
|
|
16184
|
+
},
|
|
16185
|
+
outputSchema: { ok: z.boolean().optional(), key: z.string().optional(), status: z.string().optional(), note: z.string().optional(), creator: z.any().optional() },
|
|
16186
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
16187
|
+
}, wrap(async (a) => {
|
|
16188
|
+
const key = String(a.key || '').trim();
|
|
16189
|
+
if (!key) return { content: [{ type: 'text', text: 'Pass the saved creator key (from list_swipefile).' }], isError: true };
|
|
16190
|
+
if (a.status == null && a.note == null) return { content: [{ type: 'text', text: 'Pass a status and/or a note.' }], isError: true };
|
|
16191
|
+
const s = await readSwipe();
|
|
16192
|
+
const row = s.ads.find(x => x && x.key === key);
|
|
16193
|
+
if (!row) return { content: [{ type: 'text', text: `No saved row with key ${key}. list_swipefile shows every key.` }], isError: true };
|
|
16194
|
+
if (row.platform !== 'creator' || !row.creator) return { content: [{ type: 'text', text: `${key} is a saved ad, not a creator — outreach status only applies to creators (platform "creator").` }], isError: true };
|
|
16195
|
+
const prev = row.creator.outreach || {};
|
|
16196
|
+
row.creator.outreach = { status: a.status != null ? a.status : (prev.status || 'new'), note: a.note != null ? String(a.note).slice(0, 2000) : (prev.note || ''), updatedAt: Date.now() };
|
|
16197
|
+
await writeStore(SWIPE_KEY, s);
|
|
16198
|
+
const o = row.creator.outreach;
|
|
16199
|
+
return ok(`@${row.creator.handle || key}: status ${o.status}${o.note ? ` — note: ${o.note}` : ''}.`, { ok: true, key, status: o.status, note: o.note, creator: row.creator });
|
|
16200
|
+
}));
|
|
16201
|
+
|
|
16172
16202
|
// ── PLAYBOOKS — the "what's working + the plays to run" cards, distinct from the swipefile's raw creative. Same
|
|
16173
16203
|
// server-synced store seam (`heist.playbooks.v1`, union-merged by id in adapters/sync-merge.js); the web app has a
|
|
16174
16204
|
// Playbooks tab that lists, runs and deletes them, and the Studio chat can save one. Headlessly there was no way to
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.207",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
|
-
"description": "AI ad studio and marketing MCP server with
|
|
5
|
+
"description": "AI ad studio and marketing MCP server with 791 tools. Research the ads already running in any market, generate finished image, video and UGC avatar ads, publish and schedule them to your own channels, build and manage the ad campaigns behind them, and read what they achieved. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads and ChatGPT Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and Telegram. 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"
|