hermoso 0.1.539 → 0.1.541
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 +6 -4
- package/mcp/tools.mjs +19 -5
- package/package.json +1 -1
- package/skills/hermoso-launch-app/SKILL.md +62 -0
package/README.md
CHANGED
|
@@ -133,8 +133,9 @@ just download it. Use the one piece you need, or all of it together.
|
|
|
133
133
|
## Install in one command
|
|
134
134
|
|
|
135
135
|
This repo is a plugin marketplace, a Gemini CLI extension and a skills package at once, so a coding agent takes
|
|
136
|
-
Hermoso in one line. Every install brings the same
|
|
137
|
-
`hermoso-ad-from-brand`, `hermoso-product-photoshoot`, `hermoso-marketing`, `hermoso-edit-timeline
|
|
136
|
+
Hermoso in one line. Every install brings the same seven skills (`hermoso-research`, `hermoso-generate`,
|
|
137
|
+
`hermoso-ad-from-brand`, `hermoso-product-photoshoot`, `hermoso-marketing`, `hermoso-edit-timeline`,
|
|
138
|
+
`hermoso-launch-app`), and the skills drive the `hermoso`
|
|
138
139
|
CLI through `npx`. No
|
|
139
140
|
tool list is loaded into your session: a CLI command costs nothing until it runs, and it reaches every tool.
|
|
140
141
|
The first time, your agent runs `npx -y hermoso auth login`, which opens a browser to sign in.
|
|
@@ -286,7 +287,7 @@ Connect, approve with your Hermoso account, and you are done: the full toolset w
|
|
|
286
287
|
1. **Get an account** at [app.hermoso.ai](https://app.hermoso.ai). The free tier is included; plans and credits
|
|
287
288
|
are the same ones the web Studio uses. Or skip the browser entirely and let your agent sign itself up on a paid
|
|
288
289
|
plan with `POST /v1/signup` (above).
|
|
289
|
-
2. **Install the plugin.** It adds the
|
|
290
|
+
2. **Install the plugin.** It adds the seven Hermoso skills, which drive the `hermoso` CLI through `npx`:
|
|
290
291
|
|
|
291
292
|
```bash
|
|
292
293
|
claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso
|
|
@@ -514,7 +515,7 @@ the package, so they need no key, no network and no sign-in.
|
|
|
514
515
|
|
|
515
516
|
## 3. Skills: what a coding agent installs
|
|
516
517
|
|
|
517
|
-
`skills/` holds
|
|
518
|
+
`skills/` holds seven installable skills:
|
|
518
519
|
|
|
519
520
|
| Skill | What it does |
|
|
520
521
|
| --- | --- |
|
|
@@ -524,6 +525,7 @@ the package, so they need no key, no network and no sign-in.
|
|
|
524
525
|
| `hermoso-product-photoshoot` | A real product photo composited into studio, lifestyle or hero scenes |
|
|
525
526
|
| `hermoso-marketing` | The whole loop: research, create, publish and schedule, paid campaigns, measure |
|
|
526
527
|
| `hermoso-edit-timeline` | The manual for composing any video edit or transition with `edit_timeline` |
|
|
528
|
+
| `hermoso-launch-app` | Launch and market an app or site you just built: brand, launch ads, a week of scheduled posts |
|
|
527
529
|
|
|
528
530
|
They are what every one-command install at the top of this page adds, in Claude Code, Codex, Gemini CLI and
|
|
529
531
|
through `npx skills add`. From a clone, copying works too:
|
package/mcp/tools.mjs
CHANGED
|
@@ -674,7 +674,13 @@ export function noteBrandOf(tool, args) {
|
|
|
674
674
|
for (const k of HERMOSO_BRAND_KEYS.get(tool) || []) { const v = args?.[k]; if (typeof v === 'string' && v.trim()) return v.trim().slice(0, 120); }
|
|
675
675
|
return '';
|
|
676
676
|
}
|
|
677
|
-
|
|
677
|
+
// …AND AT MOST ONE NEXT STEP, WHICHEVER IT IS (2026-11-14, lib/agent-nudge.mjs). `nudge: 1` tells the server this twin reads
|
|
678
|
+
// `next` off the answer: the one step it chose for this account (save a brand first, then connect a channel), with the
|
|
679
|
+
// sentence to say, each offered at most once in seven days and never once done. The server words the text, the step and
|
|
680
|
+
// the hint, so a step added later needs no new twin; a server that predates this answers the connect prompt as before.
|
|
681
|
+
// `raw` marks a call on the raw model lane, which gets no note of ours about a brand; `asked` says this session's
|
|
682
|
+
// first-run note already asked for their website, so the same session is not asked twice.
|
|
683
|
+
export async function withConnectPrompt(tool, r, post = apiPost, brand = '', opts = {}) {
|
|
678
684
|
try {
|
|
679
685
|
const kind = finishedRenderKind(tool, r);
|
|
680
686
|
if (!kind) return r;
|
|
@@ -683,10 +689,16 @@ export async function withConnectPrompt(tool, r, post = apiPost, brand = '') {
|
|
|
683
689
|
const sc = r.structuredContent || {};
|
|
684
690
|
const job = String(sc.jobId || sc.id || '').slice(0, 80);
|
|
685
691
|
const client = store ? String(store.client || '') : 'hermoso';
|
|
686
|
-
const d = await Promise.race([Promise.resolve(post('/api/mcp/connect-prompt', { tool, kind, job, client, ...(brand ? { brand } : {}) })).catch(() => null), new Promise((res) => { const t = setTimeout(() => res(null), CONNECT_PROMPT_WAIT_MS); if (t && typeof t.unref === 'function') t.unref(); })]);
|
|
692
|
+
const d = await Promise.race([Promise.resolve(post('/api/mcp/connect-prompt', { tool, kind, job, client, nudge: 1, ...(opts && opts.raw ? { raw: true } : {}), ...(opts && opts.asked ? { asked: true } : {}), ...(brand ? { brand } : {}) })).catch(() => null), new Promise((res) => { const t = setTimeout(() => res(null), CONNECT_PROMPT_WAIT_MS); if (t && typeof t.unref === 'function') t.unref(); })]);
|
|
687
693
|
if (!d || d.show !== true || typeof d.text !== 'string' || !d.text) return r;
|
|
688
|
-
const
|
|
689
|
-
|
|
694
|
+
const next = d.next && typeof d.next === 'object' && typeof d.next.step === 'string' && d.next.step ? d.next : null;
|
|
695
|
+
const hint = d.hint && typeof d.hint === 'object' && typeof d.hint.do === 'string' && d.hint.do ? d.hint : null;
|
|
696
|
+
if (next && next.step !== 'connect_channel') {
|
|
697
|
+
const out = { ...r, content: [...r.content, { type: 'text', text: d.text }], structuredContent: { ...sc, next } };
|
|
698
|
+
return hint ? withHints(out, [...hintsOf(r), hint]) : out;
|
|
699
|
+
}
|
|
700
|
+
const out = { ...r, content: [...r.content, { type: 'text', text: d.text }], structuredContent: { ...sc, connectPrompt: { channels: Array.isArray(d.channels) ? d.channels : [] }, ...(next ? { next } : {}) } };
|
|
701
|
+
return withHints(out, [...hintsOf(r), hint || { do: 'offer to post this; hand the user one of the connect links in this reply', why: 'no publishing channel is connected, and connecting needs a browser sign-in on the platform' }]);
|
|
690
702
|
} catch { return r; }
|
|
691
703
|
}
|
|
692
704
|
// THE WEEK-PLAN NOTE (2026-11-02, lib/week-plan.mjs): the offer after a first connection, and "your week is finished". The
|
|
@@ -699,6 +711,7 @@ const WEEK_PLAN_NOTE_SKIP = new Set(['plan_week', 'get_job', 'list_jobs']);
|
|
|
699
711
|
export async function withWeekPlanNote(tool, r, post = apiPost, brand = '') {
|
|
700
712
|
try {
|
|
701
713
|
if (!r || r.isError || !Array.isArray(r.content) || WEEK_PLAN_NOTE_SKIP.has(tool)) return r;
|
|
714
|
+
if (r.structuredContent && r.structuredContent.next && typeof r.structuredContent.next.step === 'string') return r; // one next step a reply (a paging cursor called `next` is not one): this note is not asked for, so it is not spent, and the next reply carries it
|
|
702
715
|
const store = mcpCtx.getStore();
|
|
703
716
|
if (store && (store.connectPromptQuiet || (store.remote && store.client === undefined))) return r;
|
|
704
717
|
const client = store ? String(store.client || '') : 'hermoso';
|
|
@@ -872,6 +885,7 @@ const wrap = (fn) => {
|
|
|
872
885
|
_fr.said = true;
|
|
873
886
|
const _n = ((_st.apiCharged || 0) + (_st.jobCharged || 0)) > 0 ? null : firstRunNote(_tool, _fr, r);
|
|
874
887
|
if (_n) {
|
|
888
|
+
if (/draft_brand/.test(String(_n.hint?.do || ''))) _fr.brandAsked = true; // this session has asked for their website: its next finished creation does not ask again (withConnectPrompt `asked`)
|
|
875
889
|
const _last = [...r.content].reverse().find((b) => b && b.type === 'text' && typeof b.text === 'string');
|
|
876
890
|
if (_last) _last.text += `\n\n${_n.text}`; else r.content.push({ type: 'text', text: _n.text });
|
|
877
891
|
return noPitchOnWidget(withHints(r, [_n.hint]));
|
|
@@ -879,7 +893,7 @@ const wrap = (fn) => {
|
|
|
879
893
|
}
|
|
880
894
|
} catch {}
|
|
881
895
|
const _noteBrand = noteBrandOf(_tool, args); // the brand this call acted on, so both notes are about it
|
|
882
|
-
return noPitchOnWidget(await withWeekPlanNote(_tool, await withConnectPrompt(_tool, withWidgetResultNote(_tool, r, hostRendersWidgets()), apiPost, _noteBrand), apiPost, _noteBrand));
|
|
896
|
+
return noPitchOnWidget(await withWeekPlanNote(_tool, await withConnectPrompt(_tool, withWidgetResultNote(_tool, r, hostRendersWidgets()), apiPost, _noteBrand, { raw: args?.raw === true, asked: mcpCtx.getStore()?.firstRun?.brandAsked === true }), apiPost, _noteBrand));
|
|
883
897
|
}
|
|
884
898
|
catch (e) {
|
|
885
899
|
try { recordToolOutcome(_tool, { ok: false, ms: Date.now() - _t0 }); } catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.541",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "AI marketing agent and AI ad generator, run from your own AI agent. 1233 tools. No markup on models on Starter and up. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on any plan; Free includes 5 posts a month and every paid plan posts without a limit. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads, ChatGPT Ads and AppLovin Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky, Telegram, WordPress, Discord, Slack, Whop and Google Business Profile. INSTAGRAM DM AUTOMATIONS: a keyword comment, a DM, a story reply or an ig.me link gets an automatic private reply from the brand. 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",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hermoso-launch-app
|
|
3
|
+
description: >-
|
|
4
|
+
Launch and market an app or site you just built, using Hermoso. Sets up the brand from the app, makes launch
|
|
5
|
+
ads and posts, schedules them on the owner's own social channels and, if asked, builds paused ad campaigns.
|
|
6
|
+
Use when the user says "launch this", "market my app", "get me users", "make launch posts", "promote what we
|
|
7
|
+
just built", or finishes a build in an app builder (Lovable, Replit, Bolt, v0, Base44) and asks what is next.
|
|
8
|
+
NOT for: building the app itself, or a single image from a prompt (use hermoso-generate).
|
|
9
|
+
argument-hint: "[the app's URL or one line about what it does, e.g. 'https://myapp.com, a habit tracker for runners']"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Hermoso: launch the app you just built
|
|
13
|
+
|
|
14
|
+
You built something. This gets it in front of people: a saved brand, launch creative, a week of scheduled posts
|
|
15
|
+
and, only if the user asks, paid campaigns created paused.
|
|
16
|
+
|
|
17
|
+
## Before you start
|
|
18
|
+
- Hermoso has to be connected to this host as an MCP server (URL `https://app.hermoso.ai/mcp`, sign in once).
|
|
19
|
+
If its tools are not available, send the user to https://hermoso.ai/connect/ and pick their host
|
|
20
|
+
(Lovable, Replit, Bolt, v0 and Base44 each have a page with the exact clicks).
|
|
21
|
+
- In a terminal there is no MCP server to connect: the same tools run through the Hermoso CLI. Sign in once with
|
|
22
|
+
`npx -y hermoso auth login` (opens a browser; nothing to paste). On a machine with no browser:
|
|
23
|
+
`npx -y hermoso auth login --token <your key>`, using a key from app.hermoso.ai under **MCP & CLI**. Then
|
|
24
|
+
`hermoso tools --search <what you want>` finds a tool, `hermoso tools <name>` prints its arguments and
|
|
25
|
+
`hermoso call <name> --json '{...}'` runs it (after `npm install -g hermoso`, or prefix each with `npx -y`).
|
|
26
|
+
No account at all? An agent can sign itself up on a paid plan with `POST /v1/signup` at app.hermoso.ai; see
|
|
27
|
+
the Hermoso README.
|
|
28
|
+
- A tool you do not see in your list still exists: `find_tools` searches every Hermoso tool and `call_tool`
|
|
29
|
+
runs any of them by name.
|
|
30
|
+
- The connector works while you build. It is not part of the published app, so never wire Hermoso calls into
|
|
31
|
+
the app's own code unless the user asks for an API integration.
|
|
32
|
+
|
|
33
|
+
## The launch, in order
|
|
34
|
+
Do only the steps the user wants. Say what you are about to do before anything that spends credits or posts.
|
|
35
|
+
|
|
36
|
+
1. **Know the product.** Use what you already know from the build: what the app does, who it is for, its name,
|
|
37
|
+
its live URL. Ask only for what is missing. One line is enough.
|
|
38
|
+
2. **Save the brand.** If the app has a public URL, call `draft_brand` with it so every ad matches the app's
|
|
39
|
+
look and voice. No URL yet? Call `draft_brand` with a short description instead. `get_brand` shows what is
|
|
40
|
+
already saved; do not draft twice.
|
|
41
|
+
3. **See what works in this market (optional, costs credits).** `find_competitors` then `research_ads`, and
|
|
42
|
+
report the two or three hooks that keep showing up. Skip this if the user just wants posts.
|
|
43
|
+
4. **Make the launch creative.** Three is a good first set: a static launch ad, a carousel or second static
|
|
44
|
+
with a different angle, and one short vertical video if the user wants video. `generate_image` and
|
|
45
|
+
`render_ad` pick sound defaults when you do not name a model. Video takes a few minutes: read the job with
|
|
46
|
+
`get_job` and never call it done before it finishes. Show the user each result.
|
|
47
|
+
5. **Check the channels.** `list_connectors` says which social accounts are connected. None connected is not
|
|
48
|
+
an error: give the user the connect link from the tool result, or tell them to open Hermoso and connect
|
|
49
|
+
the channels they want under Connectors. Do not post anywhere the user did not name.
|
|
50
|
+
6. **Schedule the week.** Show the exact caption and the exact account for each post and get a yes. Then
|
|
51
|
+
`schedule_post`, one a day, at the times the user picks or mornings in their time zone. `list_scheduled`
|
|
52
|
+
reads the queue back. Never rewrite or shorten a caption the user wrote.
|
|
53
|
+
7. **Paid ads, only if asked.** Campaigns are created paused on the user's own ad accounts and read back
|
|
54
|
+
before you describe them. Turning one on is the user's decision, stated in their own words.
|
|
55
|
+
8. **Report.** What was made (with links), what is scheduled and when, what is waiting on the user (a channel
|
|
56
|
+
to connect, a campaign to turn on), and the one thing to do next week: read what performed with
|
|
57
|
+
`post_performance` and make more of that.
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
- Nothing is published, scheduled or spent on ads without the user saying so in this conversation.
|
|
61
|
+
- If the free credits run out, say so plainly and stop. Do not swap in a cheaper version of what was asked.
|
|
62
|
+
- Use the app's real name, screens and URL. Do not invent features, prices, reviews or numbers for the ads.
|