hermoso 0.1.23 → 0.1.25
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 +68 -22
- package/bin/hermoso.mjs +1 -0
- package/mcp/client.mjs +17 -2
- package/mcp/tools.mjs +910 -18
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# Hermoso — MCP, CLI & Skills
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
|
|
3
|
+
Run your whole marketing operation from **any AI agent**: Claude Code, Claude.ai, Cursor, Codex, or your own
|
|
4
|
+
scripts. Research the ads already winning in a market, generate finished image & video ads (your real product
|
|
5
|
+
composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad
|
|
6
|
+
campaigns behind them — all over [MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
|
|
7
|
+
|
|
8
|
+
**247 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
|
|
9
|
+
catalog with exact per-render credit costs plus the full capability map.
|
|
7
10
|
|
|
8
11
|
## Instant: the hosted Claude.ai connector
|
|
9
12
|
|
|
10
13
|
Paste **`https://app.hermoso.ai/mcp`** into Claude → Settings → Connectors → *Add custom connector*, approve with
|
|
11
|
-
your Hermoso account, done — the full
|
|
14
|
+
your Hermoso account, done — the full toolset with your saved brand context, billed to your plan.
|
|
12
15
|
|
|
13
16
|
## Quickstart for Claude Code / Cursor / scripts (2 minutes)
|
|
14
17
|
|
|
@@ -27,8 +30,8 @@ learned memory you set up in the web app apply automatically (`get_brand` shows
|
|
|
27
30
|
|
|
28
31
|
## 1. MCP server (stdio) — Claude Code / Cursor / Codex
|
|
29
32
|
|
|
30
|
-
`hermoso mcp` runs a stdio MCP server exposing the full
|
|
31
|
-
|
|
33
|
+
`hermoso mcp` runs a stdio MCP server exposing the full toolset. The published `hermoso` package means no clone —
|
|
34
|
+
`npx -y hermoso mcp` fetches and runs it:
|
|
32
35
|
|
|
33
36
|
```bash
|
|
34
37
|
claude mcp add hermoso -e HERMOSO_TOKEN=hmk_… -- npx -y hermoso mcp
|
|
@@ -43,25 +46,67 @@ Cursor / Codex — add to `mcp.json` (Codex uses the TOML equivalent):
|
|
|
43
46
|
|
|
44
47
|
Then ask your agent: *“Generate an image ad with Hermoso.”*
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
49
|
+
### What the 247 tools cover
|
|
50
|
+
|
|
51
|
+
**Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
|
|
52
|
+
Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
|
|
53
|
+
social (`search_tiktok`, `search_instagram`, `search_youtube`, `search_reddit`, `search_threads`);
|
|
54
|
+
`scrapecreators_fetch`, `mine_angles`, `analyze_video`, `check_ad_policy`, `list_skills` / `get_skill`.
|
|
55
|
+
|
|
56
|
+
**Create** — `draft_brand` → `plan_ad` → `render_ad` (the Studio quality pipeline: composited text, clean speech,
|
|
57
|
+
music, brand end card), or `generate_image` / `generate_video` / `generate_avatar` (UGC creators + lip-sync).
|
|
58
|
+
Also `make_template_ad` (native HTML ad formats), `make_explainer`, `product_sizzle`, `make_thumbnail`,
|
|
59
|
+
`remix_static`, `recast_motion`, `reframe_video`, `upscale_video`, `dub_video`, `change_voice`, `finish_video`,
|
|
60
|
+
`fix_beat`, `stitch_video`, `clip_video`, `post_edit`, plus `plan_variations` + `score_ad` to fan out and rank.
|
|
61
|
+
**Length is yours to set:** pass `durationSeconds` to `plan_ad` and the storyboard is *authored* to it — ≤15s
|
|
62
|
+
renders as one continuous clip, longer is stitched from acts (40s = 15+15+10), never time-compressed.
|
|
63
|
+
|
|
64
|
+
**Raw model playground** — the full catalog (30+ image / video / voice / writing models, each with its exact
|
|
65
|
+
per-render credit cost) with no ad framing: `generate_image` / `generate_video` with `useBrand:false`,
|
|
66
|
+
`generate_voice`, `generate_text`.
|
|
67
|
+
|
|
68
|
+
**Publish to your own channels** — Facebook, Instagram and Threads (`post_to_meta`), TikTok (`post_to_tiktok`),
|
|
69
|
+
YouTube (`post_to_youtube` + `update_youtube_video`, `youtube_video_insights`, comments read/reply), X
|
|
70
|
+
(`post_to_x`, `x_post_metrics`, `x_post_insights`, `x_mentions`), LinkedIn profile **and** company Pages
|
|
71
|
+
(`post_to_linkedin`, `post_to_linkedin_page`), Pinterest (`post_to_pinterest` + boards), and Google Business
|
|
72
|
+
Profile (`post_to_google_business` — Google grants this API per project, so access must be approved before Posts
|
|
73
|
+
publish). `schedule_post` / `list_scheduled` / `cancel_scheduled` give you one content calendar across channels.
|
|
74
|
+
`upload_file` brings in any external or local media, not just Hermoso renders.
|
|
75
|
+
*X posting bills credits per API call (X charges per request); a post containing a link costs 13× one without.*
|
|
76
|
+
|
|
77
|
+
**Run the ads** — full campaign trees, built paused and read back before anything is reported, with every spend
|
|
78
|
+
change confirm-gated, on **Meta**, **Google Ads**, **LinkedIn Ads**, **Pinterest Ads**, **Microsoft Advertising**
|
|
79
|
+
and **ChatGPT Ads** (OpenAI's Advertiser API). Each has list + report + create + budget/status tools
|
|
80
|
+
(e.g. `list_google_ads_campaigns`, `google_ads_report`, `create_google_ads_campaign`, `set_google_ads_budget`,
|
|
81
|
+
`set_google_ads_status`). *X Ads are not supported — X posting only.*
|
|
82
|
+
|
|
83
|
+
**Files** — Google Drive CRUD (`save_to_drive`, `list_drive_files`, `update_drive_file`, `delete_drive_file`,
|
|
84
|
+
`create_drive_folder`), Google Sheets (`create_sheet`, `append_to_sheet`, `read_sheet`), Google Docs
|
|
85
|
+
(`create_doc`, `append_to_doc`), and OneDrive (`save_to_onedrive` + full CRUD).
|
|
86
|
+
|
|
87
|
+
**Workspace & account** — brand workspaces (`list_brands`, `create_brand`, `use_brand`, `update_brand`,
|
|
88
|
+
`delete_brand` — one account holds many brands, so an agency runs every client through here), memory
|
|
89
|
+
(`remember`, `forget`, `list_memory`), custom skills (`save_skill`, `delete_skill`), AI employees
|
|
90
|
+
(`save_employee`, `list_employees`, `set_active_employee`), team (`list_team`, `invite_member`, `remove_member`,
|
|
91
|
+
`set_role`), settings (`get_settings`, `update_settings` — including the **language** every ad, script and plan
|
|
92
|
+
is written in), connectors (`list_connectors`, `list_connector_accounts`, `set_connector_accounts`,
|
|
93
|
+
`disconnect_connector`), and billing (`hermoso_credits`, `billing_status`, `buy_credits`, `upgrade_plan`,
|
|
94
|
+
`set_auto_reload`), plus `list_jobs` / `get_job` for async renders.
|
|
95
|
+
|
|
96
|
+
**Connector accounts are picked, not guessed.** One person often administers several Facebook Pages, Google Ads
|
|
97
|
+
customers or LinkedIn company Pages. Only the accounts ticked for a brand are usable — enforced server-side, and
|
|
98
|
+
an empty selection shares nothing. Linking a *new* account is the one step that is not headless (it is an OAuth
|
|
99
|
+
consent screen, so the user does it in the app).
|
|
100
|
+
|
|
101
|
+
Render jobs queue server-side and poll to completion, returning a served URL.
|
|
57
102
|
|
|
58
103
|
## 2. CLI — the token-cheap path for terminal agents
|
|
59
104
|
|
|
60
|
-
`bin/hermoso.mjs` mirrors the tools as subprocess commands, so an agent can shell out instead of carrying a
|
|
61
|
-
tool manifest.
|
|
105
|
+
`bin/hermoso.mjs` mirrors the core tools as subprocess commands, so an agent can shell out instead of carrying a
|
|
106
|
+
fat tool manifest.
|
|
62
107
|
|
|
63
108
|
```bash
|
|
64
|
-
npm install -g
|
|
109
|
+
npm install -g hermoso # installs `hermoso`
|
|
65
110
|
hermoso capabilities # valid model ids + costs (run first)
|
|
66
111
|
hermoso create --brand "YourBrand" --product "your best-selling product" --format image
|
|
67
112
|
hermoso generate image --prompt "…" --ref ./product.png --wait
|
|
@@ -88,8 +133,9 @@ Then invoke `/hermoso-ad-from-brand an ad for yourbrand.com — our hero product
|
|
|
88
133
|
| Env | Meaning |
|
|
89
134
|
| --- | --- |
|
|
90
135
|
| `HERMOSO_API_BASE` | The Hermoso API origin (default `https://app.hermoso.ai` — set `http://localhost:3000` if you run the app yourself) |
|
|
91
|
-
| `HERMOSO_TOKEN` | Bearer
|
|
136
|
+
| `HERMOSO_TOKEN` | Bearer agent key (`hmk_…`) — required against the hosted app |
|
|
92
137
|
| `HERMOSO_PROFILE` | Brand-workspace id, for accounts with multiple brand profiles |
|
|
138
|
+
| `HERMOSO_OWNER` | Only for a brand **another account shared with you** (a team workspace): the owning account id. Set it together with `HERMOSO_PROFILE`, and set `HERMOSO_PROFILE` to that workspace's **profileUuid** — a brand's short slug is refused. Run `list_brands` (or `hermoso brands`) to print both values for every workspace you can enter. The server re-authorizes the pair on every request, so a wrong value is refused, never trusted. |
|
|
93
139
|
|
|
94
140
|
`mcp/http.mjs` is the hosted remote-connector transport (paste-a-URL into Claude.ai → Connectors). It ships in
|
|
95
141
|
this repo for transparency and refuses to mount without authenticated identity — no anonymous spend, ever.
|
package/bin/hermoso.mjs
CHANGED
|
@@ -78,6 +78,7 @@ async function main() {
|
|
|
78
78
|
process.env.HERMOSO_API_BASE = process.env.HERMOSO_API_BASE || cfg.apiBase || 'https://app.hermoso.ai';
|
|
79
79
|
if (cfg.token && !process.env.HERMOSO_TOKEN) process.env.HERMOSO_TOKEN = cfg.token;
|
|
80
80
|
if (cfg.profile && !process.env.HERMOSO_PROFILE) process.env.HERMOSO_PROFILE = cfg.profile;
|
|
81
|
+
if (cfg.owner && !process.env.HERMOSO_OWNER) process.env.HERMOSO_OWNER = cfg.owner; // shared team workspace: the owning account (server re-authorizes it)
|
|
81
82
|
|
|
82
83
|
// `hermoso mcp` → run the stdio MCP server (Claude Code / Cursor / Codex spawn this, e.g. `npx -y hermoso mcp`).
|
|
83
84
|
// It OWNS stdout as the JSON-RPC channel, so hand off immediately and print nothing to stdout here. The
|
package/mcp/client.mjs
CHANGED
|
@@ -17,11 +17,26 @@ const TOKEN = process.env.HERMOSO_TOKEN || '';
|
|
|
17
17
|
// header > key.keyProfileId > 'default' (adapters/auth/middleware.js), so always sending the header permanently
|
|
18
18
|
// masks the brand `use_brand` saved against the key — connectors on any non-default brand then look disconnected.
|
|
19
19
|
export const PROFILE = process.env.HERMOSO_PROFILE || '';
|
|
20
|
+
// SHARED TEAM WORKSPACE: the OWNING account. The web client sends this as x-hermoso-owner from PROFILE_OWNER
|
|
21
|
+
// (public/app.js ctxHeaders) whenever the active brand belongs to someone else's account; the MCP twins never did,
|
|
22
|
+
// so a member driving Hermoso headlessly resolved every brand-scoped read against their OWN empty account —
|
|
23
|
+
// resolveWs's `if (owner && owner !== own)` branch simply never ran and it fell through to the own-account path.
|
|
24
|
+
// Symptom (live 2026-07-31): 0 connectors over MCP on a workspace showing 10 in the browser, with NO error.
|
|
25
|
+
// SAFE TO SEND: the server RE-AUTHORIZES it against profile_members on every request (adapters/auth/middleware.js
|
|
26
|
+
// resolveWs), so a forged or stale value is 403'd, never trusted. Like the profile header it must stay UNSET by
|
|
27
|
+
// default — sending an owner for your own account would make resolveWs take the shared branch against yourself.
|
|
28
|
+
// PAIR IT WITH THE PROFILE UUID, not the slug: profile_members keys on profiles.id, so a client_slug is the one
|
|
29
|
+
// thing isMember() cannot match and it 403s. list_brands names both values for every workspace you can enter.
|
|
30
|
+
export const OWNER = process.env.HERMOSO_OWNER || '';
|
|
31
|
+
// The env-var prefix THIS build reads. tools.mjs is byte-identical across the two twins, so it cannot
|
|
32
|
+
// hardcode either name when it tells a user which variables to set — it asks its own client.
|
|
33
|
+
export const ENV_PREFIX = 'HERMOSO';
|
|
20
34
|
|
|
21
35
|
function headers(extra = {}) {
|
|
22
36
|
const ctx = mcpCtx.getStore();
|
|
23
37
|
const prof = ctx?.profile || PROFILE; // omitted when unpinned so the key's saved brand wins server-side
|
|
24
|
-
const
|
|
38
|
+
const own = ctx?.owner || OWNER; // the wire name is x-hermoso-owner on BOTH twins — it is the server's header, not a brand
|
|
39
|
+
const h = { 'Content-Type': 'application/json', ...(prof ? { 'x-hermoso-user': prof } : {}), ...(own ? { 'x-hermoso-owner': own } : {}), ...extra };
|
|
25
40
|
const tok = ctx?.token || TOKEN;
|
|
26
41
|
if (tok) h.Authorization = `Bearer ${tok}`;
|
|
27
42
|
return h;
|
|
@@ -131,4 +146,4 @@ export async function toRef(srcOrPath) {
|
|
|
131
146
|
return `data:${mime};base64,${buf.toString('base64')}`;
|
|
132
147
|
}
|
|
133
148
|
|
|
134
|
-
export const authState = () => ({ apiBase: API_BASE, hasToken: !!TOKEN, profile: PROFILE });
|
|
149
|
+
export const authState = () => ({ apiBase: API_BASE, hasToken: !!TOKEN, profile: PROFILE, owner: OWNER });
|