skills/inboxmate-demo/SKILL.md
Set up a personalized InboxMate demo chatbot for a sales prospect. Use when asked to create a demo, set up an InboxMate playground, or prepare a chatbot demo. Guides the full pipeline: research company, scrape content, call MCP, deliver playground URL.
npx skillsauth add psquared-development/psquared-skills inboxmate-demoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Announce to the user at the very start:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ InboxMate Demo Pipeline started. Working through 6 phases — I'll narrate each step. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Announce:
[0/5] Learning InboxMate platform capabilities...
You are new to InboxMate. Do this before anything else.
InboxMate is a white-label AI chatbot platform built by psquared. Businesses embed a chat widget on their website — visitors chat with an AI agent that knows the company's products, services, and FAQs.
The MCP server at https://app.psquared.dev/api/mcp exposes tools to create and configure these agents programmatically. All operations run against a shared demo account.
Call tools/list on the MCP server first to get the current tool list and confirm connectivity:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"notifications/initialized","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}}
Environment variables (read from .env in the current working directory at startup):
NUXT_MCP_DEMO_TOKEN — Bearer token for the InboxMate MCP serverOPENBRAND_API_KEY — API key for OpenBrand color/logo extraction (used in Phase 2f)PSQUARED_CRM_TOKEN — Bearer token for the psquared CRM API (used in Phase 5)MCP connection:
https://app.psquared.dev/api/mcpAuthorization: Bearer <NUXT_MCP_DEMO_TOKEN>Key concepts:
demo.inboxmate.psquared.devAfter confirming tools list, announce:
[0/5] Platform ready. Proceeding to prospect research.
Announce:
[1/5] Researching [Company Name]...
Before scraping, check if the website is reachable and current. Use WebFetch on the homepage.
Auto-skip if ANY of these are true:
If the website is unusable, announce:
SKIP: [Company] — [reason]. Website is not suitable for a demo.Do NOT ask the user what to do. Just skip and move on. If running as part of the batch pipeline, the batch skill handles CRM marking. If running standalone, just stop and report the skip reason — the user can decide later.
Given a company name and/or domain, fetch all of these pages (adjust paths as needed):
| Page | What to extract | |------|-----------------| | Homepage | Core value prop, headline, tagline, main CTA | | /about or /ueber-uns | Company story, team, mission, founding year | | /products or /services or /leistungen | All products/services with descriptions, features, differentiators | | /pricing or /preise | Pricing tiers, what's included, trial info | | /faq | Common questions and answers verbatim | | /contact or /kontakt | Email, phone, address, business hours, contact form | | /blog or /cases (optional) | Social proof, use cases, customer stories |
Use WebFetch on each URL. Do not skip pages — thin knowledge = bad demo.
Extract and record:
After fetching, announce:
[1/5] Research complete. Analyzing language and planning content...
Announce:
[2/5] Planning agent content and language configuration...
Look at the website content you scraped:
| Signal | Decision |
|--------|----------|
| Website is in German only | language: 'de' |
| Website is in English only | language: 'en' |
| Website has both DE and EN, or company serves both markets | language: 'multi' |
| Austrian/German/Swiss company with DE site but international product | language: 'multi' |
| English-only company, no German signals | language: 'en' |
Announce your decision:
Language: [EN / DE / MULTI] — [reason in one sentence]
Do NOT put all content into one blob. Create separate focused knowledge items per topic. Each item is independently retrieved by vector search — short, specific items win over large walls of text.
Plan these items (create all that have content):
| Item | Title | Content |
|------|-------|---------|
| 1 | [Company] – Überblick / [Company] – Overview | What the company does, who they serve, core value prop, founding story |
| 2 | Produkte & Leistungen / Products & Services | All services/products with descriptions, key features, use cases |
| 3 | Preise & Pakete / Pricing & Plans | All pricing tiers, what's included, trial/free tier info, upgrade path |
| 4 | Häufige Fragen / FAQ | All Q&A pairs from the FAQ page verbatim |
| 5 | Kontakt & Support / Contact & Support | Email, phone, address, hours, how to reach support, response time |
| 6 | Anwendungsfälle / Use Cases | Customer stories, case studies, example use cases, industries served |
| 7 (multi-lang only) | Sprachunterstützung & KI-Features / Language Support & AI Features | Explain that the assistant supports both German and English. List key InboxMate features relevant to THIS company (24/7 availability, instant answers, website integration). Frame as benefits for this company's customers. |
Write all item content in the target language(s). For
multi: write each item in the language that's most natural for that content, or bilingual if the company operates in both.
Write a crisp, specific system prompt — not a generic template. Include ALL of these:
You are [Persona Name], the AI assistant for [Company Name].
**Who you help:** [Target customer segment in 1 sentence]
**Your personality and tone:** [Derived from website tone — e.g. "friendly and approachable, using informal language (du/you)" OR "professional and precise, using formal language (Sie/formal English)"]
**Language:** [e.g. "Always respond in German (du-Form)" OR "Respond in the language the visitor uses — German or English"]
**Your goal:** Help visitors understand [Company]'s [main product/service] and guide them toward [primary CTA — e.g. "booking a free demo", "starting a free trial", "contacting the sales team"].
**What you know:** You have access to [Company]'s complete product information, pricing, FAQ, and contact details. Answer from this knowledge.
**When you can't answer:** If a question falls outside your knowledge, say so honestly and offer to connect them with the team: [contact email or "via the contact form on [domain]"].
**Never:** Invent pricing, make promises not reflected in company materials, or discuss competitors in detail.
Adjust heavily based on actual company context. This prompt should sound like it was written for THIS specific company.
Write in target language. Not generic. Reference the product, their situation, or a hook.
For multi: write separate EN and DE greetings.
ALWAYS use card tiles — never plain text pills. Cards look dramatically better and convert higher. They show an icon, a bold title, and a short description — making suggestions feel like real features instead of generic prompts.
4–5 suggestion cards that a real prospect would click. Make them irresistible — they should surface the company's best selling points.
Each question is an object with these fields:
text: The actual message sent to the chat when clicked. This is what the AI will answer.title: Short card title displayed prominently (2-5 words). This is what the user reads first.description: Brief hint about what to expect (3-5 words). Shown below the title in smaller text.icon: A Lucide icon name in kebab-case (e.g. "briefcase", "credit-card", "shield-check", "clock", "users", "rocket", "globe", "zap", "heart", "target", "lightbulb", "star", "package", "award", "calendar", "mail", "phone", "building", "trending-up", "search"). If invalid, falls back to a checkmark.Rules:
multi)text field can be longer — it's the actual question the AI answersstyle: 'cards' in the update_quick_questions callExamples for a SaaS company:
// EN
[
{ "text": "What does [Product] do and who is it for?", "title": "What is [Product]?", "description": "Features & use cases", "icon": "rocket" },
{ "text": "How much does [Product] cost? Are there different plans?", "title": "Pricing & Plans", "description": "Tiers & free trial", "icon": "credit-card" },
{ "text": "How long does setup take and do I need technical knowledge?", "title": "Getting Started", "description": "Setup in minutes", "icon": "zap" },
{ "text": "What makes [Product] different from competitors?", "title": "Why [Company]?", "description": "Key differentiators", "icon": "award" }
]
// DE
[
{ "text": "Was macht [Produkt] und für wen ist es gedacht?", "title": "Was ist [Produkt]?", "description": "Funktionen & Einsatz", "icon": "rocket" },
{ "text": "Was kostet [Produkt]? Gibt es verschiedene Pakete?", "title": "Preise & Pakete", "description": "Tarife & Testphase", "icon": "credit-card" },
{ "text": "Wie lange dauert die Einrichtung und brauche ich technisches Wissen?", "title": "Erste Schritte", "description": "Setup in Minuten", "icon": "zap" },
{ "text": "Was unterscheidet [Produkt] von der Konkurrenz?", "title": "Warum [Company]?", "description": "Die Vorteile", "icon": "award" }
]
Use the OpenBrand API to extract the brand color — it's more reliable than manual CSS inspection.
API call:
curl "https://openbrand.sh/api/extract?url=https://[domain]" \
-H "Authorization: Bearer $OPENBRAND_API_KEY"
The OPENBRAND_API_KEY is in the .env file (read it at startup along with the other tokens).
Response format:
{
"success": true,
"data": {
"brandName": "Company Name",
"logos": [{ "url": "...", "type": "favicon", "resolution": {...} }],
"colors": [
{ "hex": "#3b82f6", "usage": "primary" },
{ "hex": "#64748b", "usage": "secondary" },
{ "hex": "#ffffff", "usage": "accent" }
]
}
}
How to pick the color:
"usage": "primary" from the response#000000) or pure white (#ffffff) as the widget color — even if it's the brand's primary. These look broken in the widget. If the primary is black or white, use the secondary color instead.Also grab the logo URL from
data.logos— use the highest-resolution one aslogoUrlin the demo page.
If OpenBrand fails, fall back to manual inspection:
#1a365d)Before setting the offer, ask the user for the deadline. Do NOT assume 7 days or any other duration.
Ask:
When should the demo offer expire? Examples: "in 14 days", "2026-04-01", "end of month"
Wait for the user's answer. Convert their response to an ISO 8601 date for offerExpiresAt.
If running as part of /inboxmate-batch-demo: Ask ONCE at the start for all demos in the batch (e.g., "All demos expire on 2026-04-15"). Do NOT ask per company.
FORBIDDEN — never use any of these:
What the offer IS: A time-limited discount or special deal for signing up to InboxMate (the chatbot product). The countdown shows when this deal expires.
Use one of these patterns:
DE: "Jetzt starten und bis zu 50% Rabatt sichern" / "Sonderkonditionen für Ihren KI-Chatbot — nur bis [date]" / "Ihren KI-Assistenten jetzt aktivieren — exklusive Konditionen"
EN: "Start now and save up to 50% in your first year" / "Special pricing for your AI chatbot — limited time" / "Activate your AI assistant — exclusive terms available"
customMessage: 1–2 sentences speaking directly to the prospect: "Wir haben diesen Demo-Bot speziell für [Company] konfiguriert. Probier ihn aus!"
After planning, show a summary to the user:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ PLAN READY — confirm before building: Company: [name] Language: [en/de/multi] Knowledge items: [N items listed by title] Quick questions: [list] Color: [hex] Offer: [offerText] Deadline: [offerExpiresAt] Proceeding to build in 5 seconds unless you say stop. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Announce:
[3/5] Building agent in InboxMate...
Use individual MCP tools — do NOT use quick_setup_demo for the full pipeline. It only supports one knowledge item. We need multiple.
{
"method": "tools/call",
"params": {
"name": "create_agent",
"arguments": {
"name": "[Company] Assistant",
"prompt": "[full system prompt from 2c]",
"primaryColor": "[hex from 2f]",
"greetingMessage": "[EN greeting from 2d — omit if DE-only]",
"greetingMessageDe": "[DE greeting from 2d — omit if EN-only]",
"buttonIcon": "[choose based on company personality — see guide below]",
"buttonShape": "[circle, squared, or icon-only — see guide below]",
"agentIconType": "[choose based on company personality — see guide below]",
"widgetPresence": "[shimmer or calm — see guide below]",
"uiLang": "[en, de, or multi — must match language from Phase 2]"
}
}
}
Widget appearance guide — ALWAYS set these fields:
| Field | How to choose |
|-------|--------------|
| buttonIcon | The floating button visitors click to open chat. ONLY these values are valid: messageCircle (friendly default), messageSquare, sparkles (creative/modern), support (headphones, support-focused), help (FAQ-heavy), inboxmate (InboxMate branding), heart (warm/personal), zap (tech/fast), globe (international), wave, brain, lightbulb, compass, star, shield, robot, mascot. Any other value will render as a broken circle. Do NOT invent icon names like shoppingBag, truck, home, car, music etc. — they don't exist in the widget. |
| agentIconType | The avatar shown next to AI messages in the chat. Options: inboxmate (branded, good default), avatar (human-like, good for personal brands), bot (techy). Pick one that DIFFERS from buttonIcon. |
| buttonShape | circle = round floating button (default, works everywhere). squared = rounded square (modern/minimal). icon-only = just the icon, no background (sleek/subtle). |
| widgetPresence | calm = no animations, professional (corporate/B2B sites). shimmer = subtle glow effects (modern/creative brands). Default to calm for most business sites. |
Rules:
buttonIcon and agentIconType MUST be different from each otherrobot — choose based on the company's personality and industrybuttonIcon: "messageCircle", agentIconType: "inboxmate", buttonShape: "circle", widgetPresence: "calm"Save
agentIdfrom the response. Announce:Agent created: [agentId]
{
"method": "tools/call",
"params": {
"name": "create_knowledge_bucket",
"arguments": {
"name": "[Company] Knowledge",
"description": "Website knowledge base for [Company] — [N] items"
}
}
}
Save
bucketId. Announce:Knowledge bucket created: [bucketId]
For each item planned in 2b, call add_to_bucket separately.
CRITICAL: Always include sourceUrl. This enables source citations in the chatbot — the wow effect for prospects. The URL must point to the actual page the content was scraped from (e.g. https://company.at/leistungen for the services item, https://company.at/kontakt for the contact item). If content came from the homepage, use the homepage URL. Never omit sourceUrl.
{
"method": "tools/call",
"params": {
"name": "add_to_bucket",
"arguments": {
"bucketId": "[bucketId]",
"title": "[item title]",
"content": "[item content — focused, complete, in target language]",
"sourceUrl": "https://[domain]/[exact page path this content came from]"
}
}
}
Announce after each:
Knowledge item added: "[title]" (source: [url])Wait for each call to complete before the next — do NOT batch these.
Faster alternative: Instead of adding items one by one, use the
scrape_and_build_knowledgeMCP tool which scrapes multiple URLs in one call and creates knowledge items with real sourceUrls automatically:{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "scrape_and_build_knowledge", "arguments": { "bucketId": "[bucketId]", "urls": ["url1", "url2", "url3", ...], "agentId": "[agentId]" } } }This is recommended for 5+ URLs. It handles scraping, chunking, and embedding in one operation.
If language is multi: add one extra knowledge item to the bucket:
"Sprachunterstützung / Language Support"This AI assistant supports both German and English. It responds in whatever language the visitor uses.
Key features relevant for [Company]:
- Available 24/7, responds instantly in German or English
- Trained on [Company]'s complete product and service information
- Handles common customer questions so your team doesn't have to
- Guides visitors toward [primary CTA]
- Escalates to your team for complex inquiries
Use cases for [Company]:
[Write 2-3 specific use cases based on what you know about the company]
Content quality rules:
{
"method": "tools/call",
"params": {
"name": "set_knowledge",
"arguments": {
"agentId": "[agentId]",
"knowledgeBucketIds": ["[bucketId]"]
}
}
}
{
"method": "tools/call",
"params": {
"name": "update_quick_questions",
"arguments": {
"agentId": "[agentId]",
"style": "cards",
"questionsEn": [
{ "text": "[message sent to chat]", "title": "[Card Title]", "description": "[3-5 word hint]", "icon": "[lucide-icon-name]" },
{ "text": "[message sent to chat]", "title": "[Card Title]", "description": "[3-5 word hint]", "icon": "[lucide-icon-name]" }
],
"questionsDe": [
{ "text": "[Nachricht an den Chat]", "title": "[Karten-Titel]", "description": "[3-5 Wort Hinweis]", "icon": "[lucide-icon-name]" },
{ "text": "[Nachricht an den Chat]", "title": "[Karten-Titel]", "description": "[3-5 Wort Hinweis]", "icon": "[lucide-icon-name]" }
]
}
}
}
For EN-only: use
questionsEnonly, leavequestionsDeas[]. For DE-only: usequestionsDeonly, leavequestionsEnas[]. Always setstyle: "cards"— this enables the rich card display in the widget.
After the demo page is created (Phase 4), the MCP automatically calls update_widget_style
to set showOnPages: ['/?id=<demoId>']. This is handled by the create_demo_page tool.
You do NOT need to call update_widget_style manually for this.
{
"method": "tools/call",
"params": {
"name": "publish_agent",
"arguments": { "agentId": "[agentId]" }
}
}
Announce:
Agent published and live.
Announce:
[4/5] Creating demo page...
{
"method": "tools/call",
"params": {
"name": "create_demo_page",
"arguments": {
"agentId": "[agentId]",
"companyName": "[Company Name]",
"companyDomain": "[domain.com]",
"logoUrl": "[logo URL if found — otherwise omit]",
"offerText": "[from 2g]",
"offerExpiresAt": "[ISO date 7 days from today]",
"customMessage": "[from 2g]",
"language": "[en or de — must match the language you chose in Phase 2]",
"useCases": [
{ "text": "[Use case 1 — specific to this company, 1 sentence]", "icon": "[lucide-icon-name]" },
{ "text": "[Use case 2 — specific to this company, 1 sentence]", "icon": "[lucide-icon-name]" },
{ "text": "[Use case 3 — specific to this company, 1 sentence]", "icon": "[lucide-icon-name]" },
{ "text": "[Use case 4 — optional, if relevant]", "icon": "[lucide-icon-name]" }
]
}
}
}
The MCP will automatically restrict the widget to only show on
/?id=<demoId>— no extra step needed.
Save
demoIdandplaygroundUrl.
Announce:
[5/6] Creating CRM opportunity...
Create an opportunity in the CRM so the demo enters the review pipeline:
curl -s -X POST https://crm.psquared.dev/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PSQUARED_CRM_TOKEN" \
-d "{\"query\":\"mutation { createOpportunity(data: { name: \\\"[Company Name] — InboxMate Demo\\\", stage: SCREENING, demoStatus: PENDING_REVIEW, demoUrl: { primaryLinkUrl: \\\"[playgroundUrl]\\\" }, companyId: \\\"[companyId]\\\" }) { id name stage demoStatus } }\"}"
Note: The
companyIdcomes from the CRM. If this company doesn't exist in the CRM yet (standalone demo, not from batch pipeline), look it up first or create it.
Announce:
Opportunity created at SCREENING / PENDING_REVIEW — ready for /review-demos
Announce:
[6/6] Done!
Output this summary to the user:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DEMO READY — [Company Name]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Playground URL (share this with the prospect):
→ [playgroundUrl]
What was configured:
Language: [en / de / multi]
Brand color: [hex]
System prompt: [first 2 sentences of the prompt]
Greeting (EN): [greeting]
Greeting (DE): [greeting or "—"]
Quick questions: [list, language-appropriate]
Knowledge items: [N items — list titles]
Offer: [offerText] (expires [date])
Agent ID: [agentId] (for follow-up adjustments)
Demo ID: [demoId]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
If the demo needs changes after delivery, use these individual tools:
| What to change | Tool |
|----------------|------|
| System prompt | update_prompt |
| Quick questions | update_quick_questions (card objects with text/title/description/icon, set style: "cards") |
| Color, greeting, domain whitelist | update_widget_style |
| Add more knowledge | add_to_bucket with the existing bucketId |
| Republish after changes | publish_agent |
Run through this checklist mentally before Phase 5:
#000000) or pure white (#ffffff)sourceUrl — the exact page URL it was scraped from (enables source citations in chat)buttonIcon is set and matches company personality — NOT defaulting to robotagentIconType is set and DIFFERS from buttonIconbuttonShape and widgetPresence are setdemo.inboxmate.psquared.dev (auto-set by the platform)language matches the company's website languageuseCases as objects with { text, icon } — not plain stringscreate_demo_page)tools
Generate a polished psquared client offer as a multi-page PDF (title, project description, screenshots, Angebot/pricing, AGB). Walks the user through gathering inputs (or accepts a JSON config), renders branded HTML templates with Playwright in two passes (title page edge-to-edge + body pages with margins and pagination), then merges with pdf-lib.
data-ai
Create email drafts for approved InboxMate demos. Verifies all demos are ready, pulls contacts from CRM, creates CRM tasks, and creates draft emails via the notification service. Run after /review-demos has processed all pending demos.
development
Audit or fix SEO issues for a single website or page. Checks meta tags, structured data, technical SEO, content quality, i18n, and AI readiness using only WebFetch — no external APIs. Pass a URL and mode (audit or fix) as parameters.
development
Run SEO audit or fix across all psquared websites autonomously. Dispatches parallel agents for psquared.dev, inboxmate.psquared.dev, ki-linz.at, and agenthub.psquared.dev, then presents a combined report. Pass mode (audit or fix) as parameter.