hermoso 0.1.188 → 0.1.190
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 +7 -2
- package/mcp/tools.mjs +37 -1
- 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
|
+
**746 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,
|
|
@@ -94,6 +94,11 @@ Claude in Chrome and clients like it complete it unattended today. Everything el
|
|
|
94
94
|
and `upgrade_plan` mint a ready-to-pay link for more credits or a bigger plan, and `billing_status` reads the
|
|
95
95
|
balance any time.
|
|
96
96
|
|
|
97
|
+
**An agent with its own payment credential can pay with no human at all.** `POST /api/billing/machine-payment`
|
|
98
|
+
with `{"packId": "pack-1k"}` answers HTTP 402 carrying a `WWW-Authenticate: Payment` challenge (Stripe, through the
|
|
99
|
+
Machine Payments Protocol); pay the challenge and retry, and the same credit pack lands on the same balance.
|
|
100
|
+
`GET /api/billing/config` lists the packs under `machinePayments`. Same packs, same prices, no per-call billing.
|
|
101
|
+
|
|
97
102
|
**The agentic path takes a paid plan.** Any of them. The free plan is there for a person signing up at
|
|
98
103
|
[app.hermoso.ai](https://app.hermoso.ai), and asking for it here returns a refusal that says so. Nothing is
|
|
99
104
|
created until the payment completes, so an unpaid signup leaves no account behind and charges nothing.
|
|
@@ -163,7 +168,7 @@ block entirely if you signed in above; it is there for CI, where the process can
|
|
|
163
168
|
|
|
164
169
|
Then ask your agent: *“Generate an image ad with Hermoso.”*
|
|
165
170
|
|
|
166
|
-
### What the
|
|
171
|
+
### What the 746 tools cover
|
|
167
172
|
|
|
168
173
|
**Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
|
|
169
174
|
Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
|
package/mcp/tools.mjs
CHANGED
|
@@ -15352,9 +15352,20 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
15352
15352
|
if (!memory.length) return ok('Memory is empty for this workspace.', { memory: [] });
|
|
15353
15353
|
return ok(`${memory.length} memory item(s):\n` + memory.map(m => ` • [${m.category}] ${m.text}${m.id ? ` (${m.id})` : ''}`).join('\n'), { memory });
|
|
15354
15354
|
}));
|
|
15355
|
+
// MEMORY HYGIENE (2026-09-02) — byte-identical copies of lib/memory-hygiene.mjs's two regexes (this file ships without
|
|
15356
|
+
// lib/). tools/memory-hygiene-check.mjs pins the parity. Memory holds brand facts, never product/API mechanics or PII.
|
|
15357
|
+
const MEMORY_MECHANICS_RE = /\b(endpoint|API\b|APIs\b|webhook|rate[ -]?limit|access token|OAuth|graph\.[a-z]+|sandbox|dev(?:elopment)? mode|HTTP \d{3}|\b4\d\d\b(?: error)?|error (?:message|text|code)|'reduce the amount of data'|not (?:yet )?supported|deprecated|verified \d{4}-\d{2}-\d{2}|on this connection|Hermoso'?s? (?:own|publish record|tool|MCP|CLI)|(?:no|missing|lacks?) '?[\w ]{0,30}'? ?endpoint|tool (?:returns?|output|call)|(?:returns?|come back|comes back) (?:null|empty|zeros?|truncated|full bodies|counts only)|omit(?:s|ted)? zero rows|permission (?:edge|rows?|list|request)|subscribed_fields|paging cursor|pagination)\b/i;
|
|
15358
|
+
const MEMORY_PII_RE = /(\+\d[\d\s().-]{7,}\d)|(\(?\b\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\b)|([\w.+-]+@[\w-]+\.[\w.-]+)/;
|
|
15359
|
+
function memoryNoteVerdict(text) {
|
|
15360
|
+
const t = String(text || '').trim();
|
|
15361
|
+
if (!t) return { ok: false, reason: 'empty' };
|
|
15362
|
+
if (MEMORY_PII_RE.test(t)) return { ok: false, reason: 'pii', message: 'Not saved: Memory never holds a phone number or an email address.' };
|
|
15363
|
+
if (MEMORY_MECHANICS_RE.test(t)) return { ok: false, reason: 'mechanics', message: 'Not saved: that is about how Hermoso or a platform API behaves, not about the brand. Memory holds brand, audience, offer, taste and working preferences only — product behaviour lives in the tools themselves.' };
|
|
15364
|
+
return { ok: true };
|
|
15365
|
+
}
|
|
15355
15366
|
server.registerTool('remember', {
|
|
15356
15367
|
title: 'Remember a fact',
|
|
15357
|
-
description: 'Save a durable fact or PREFERENCE about the brand, audience, or the user’s creative TASTE (e.g. “audience is first-time homebuyers”, “prefers bold lime accents”, “always captions off”) into the workspace Memory so it shapes FUTURE ads. For lasting things, not one-off requests. Merges into the existing Memory (never overwrites); de-dupes on identical text.',
|
|
15368
|
+
description: 'Save a durable fact or PREFERENCE about the brand, audience, or the user’s creative TASTE (e.g. “audience is first-time homebuyers”, “prefers bold lime accents”, “always captions off”) into the workspace Memory so it shapes FUTURE ads. For lasting things, not one-off requests. Merges into the existing Memory (never overwrites); de-dupes on identical text. NEVER how Hermoso, a tool, a connector or a platform API behaves (what a call returns, errors, permissions, limits, ids) and never a phone number or email — those are refused.',
|
|
15358
15369
|
inputSchema: {
|
|
15359
15370
|
text: z.string().describe('the fact/preference, concise'),
|
|
15360
15371
|
category: z.string().optional().describe('short bucket: Brand, Audience, Taste, Do, Don’t, or Preference (default General)'),
|
|
@@ -15363,6 +15374,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
15363
15374
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
15364
15375
|
}, wrap(async (a) => {
|
|
15365
15376
|
const text = String(a.text || '').trim(); if (!text) return { content: [{ type: 'text', text: 'Nothing to remember — pass text.' }], isError: true };
|
|
15377
|
+
{ const v = memoryNoteVerdict(text); if (!v.ok) return { content: [{ type: 'text', text: v.message }], isError: true }; }
|
|
15366
15378
|
let list = await readStore('heist.memory.v1'); if (!Array.isArray(list)) list = [];
|
|
15367
15379
|
if (list.some(m => m && String(m.text || '').toLowerCase() === text.toLowerCase())) return ok(`Already in memory: “${text}”.`, { ok: true });
|
|
15368
15380
|
const item = { id: newId('m'), text, category: String(a.category || 'General').trim() || 'General', source: 'mcp', createdAt: Date.now() };
|
|
@@ -15392,6 +15404,30 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
15392
15404
|
// died inside one conversation, while the web user's own board sat untouched beside them.
|
|
15393
15405
|
// These are typed writers on purpose (see the store_set note above): they mint ids, de-dupe on the ad `key`, respect
|
|
15394
15406
|
// the per-store caps and write the two-level tombstone a genuine delete needs.
|
|
15407
|
+
server.registerTool('tidy_memory', {
|
|
15408
|
+
title: 'Tidy the Memory list',
|
|
15409
|
+
description: 'Clean up and consolidate the workspace Memory: drops entries that are about how Hermoso, a tool, a connector or a platform API behaves (product behaviour, not the brand), drops phone numbers and emails, and merges near-duplicate facts into one sentence each. Call with no argument to get the PROPOSAL (what would be removed and merged, with reasons) — nothing changes. Call again with confirm:true to apply it through the same typed writers the app uses (deletes carry tombstones so they stay deleted on every device). One small model call; a few credits.',
|
|
15410
|
+
inputSchema: { confirm: z.boolean().optional().describe('true to APPLY the proposal; omit to only see it') },
|
|
15411
|
+
outputSchema: { scanned: z.number().optional(), remove: z.array(z.any()).optional(), merge: z.array(z.any()).optional(), applied: z.boolean().optional() },
|
|
15412
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
15413
|
+
}, wrap(async (a) => {
|
|
15414
|
+
let list = await readStore('heist.memory.v1'); if (!Array.isArray(list)) list = [];
|
|
15415
|
+
if (!list.length) return ok('Memory is empty — nothing to tidy.', { scanned: 0, remove: [], merge: [], applied: false });
|
|
15416
|
+
const r = await apiPost('/api/memory/tidy', { items: list.map(m => ({ id: m.id, text: m.text, category: m.category })) });
|
|
15417
|
+
const d = r?.data || { remove: [], merge: [] };
|
|
15418
|
+
const byId = new Map(list.map(m => [String(m.id), m]));
|
|
15419
|
+
const lines = [];
|
|
15420
|
+
for (const x of d.remove || []) lines.push(`- remove [${x.reason}]: ${String(byId.get(x.id)?.text || x.id).slice(0, 140)}`);
|
|
15421
|
+
for (const m of d.merge || []) lines.push(`- merge ${m.ids.length} → “${m.text.slice(0, 140)}”`);
|
|
15422
|
+
if (!lines.length) return ok(`Scanned ${list.length} memories — nothing to remove or merge.`, { scanned: list.length, remove: [], merge: [], applied: false });
|
|
15423
|
+
if (!a.confirm) return ok(`Proposal over ${list.length} memories (nothing changed — call again with confirm:true to apply):\n${lines.join('\n')}`, { scanned: list.length, remove: d.remove, merge: d.merge, applied: false });
|
|
15424
|
+
const gone = new Set([...(d.remove || []).map(x => String(x.id)), ...(d.merge || []).flatMap(m => m.ids.map(String))]);
|
|
15425
|
+
for (const id of gone) await tombstone('heist.memory.v1', id);
|
|
15426
|
+
const merged = (d.merge || []).map(m => ({ id: newId('m'), text: m.text, category: m.category || 'General', source: 'tidy', createdAt: Date.now() }));
|
|
15427
|
+
await writeStore('heist.memory.v1', [...merged, ...list.filter(m => m && !gone.has(String(m.id)))].slice(0, 200));
|
|
15428
|
+
return ok(`Tidied: removed ${gone.size - (d.merge || []).flatMap(m => m.ids).length}, merged ${(d.merge || []).length} group(s). ${list.length} → ${list.length - gone.size + merged.length} memories.\n${lines.join('\n')}`, { scanned: list.length, remove: d.remove, merge: d.merge, applied: true });
|
|
15429
|
+
}));
|
|
15430
|
+
|
|
15395
15431
|
const SWIPE_KEY = 'adInspo.swipefile.v1';
|
|
15396
15432
|
const swipeEmpty = () => ({ collections: [{ id: 'default', name: 'My Swipefile' }], activeId: 'default', ads: [] });
|
|
15397
15433
|
// Mirror the webapp's Swipe.read(): anything that isn't the {collections[], ads[]} shape is treated as an empty
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.190",
|
|
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 746 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"
|