skills/create-offer/SKILL.md
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.
npx skillsauth add psquared-development/psquared-skills create-offerInstall 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:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ create-offer started. Checking environment... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
A multi-page A4 PDF with psquared branding:
paymentSummary (e.g. 50/50 schedule), optional notes.terms.extra rendered as raw HTML so it supports its own <p> tags), signature block with both parties.Brand color is #6b46c1 (purple) with #9f7aea light tint accents. Inter font fallback chain.
The reference example is examples/sanacom-example.json — use it as your starting template when copy-pasting structure for a new offer.
/create-offer [path-to-config.json] [output.pdf]
examples/sanacom-example.json). If omitted, this skill will interview the user and write the file itself before building../offer-<client-slug>.pdf in the cwd.cd /Users/<you>/Documents/psquared/psquared-skills/skills/create-offer
bash setup.sh
setup.sh is idempotent — verifies Node >= 18, runs npm install (Playwright + pdf-lib) if missing, ensures Playwright Chromium is downloaded.
Announce:
Setup OK. Playwright + Chromium + pdf-lib ready.
If the user passed a JSON path, skip this step and jump to STEP 2.
Otherwise interview the user. Ask in batches, not one question at a time. The minimum required fields are:
| Field | Example |
|-------|---------|
| client.name | "sanacom Unternehmensberatung e.U." (legal entity!) |
| client.contactPerson | "Mag. Daniela Gruber" |
| client.address | "Kirchenwagnerweg 6b, 5071 Wals bei Salzburg" |
| client.email | "[email protected]" |
| offer.title | Short product name, e.g. "Sanacom Categorizer" |
| offer.subtitle | One-sentence what-it-does |
| offer.eyebrow | "Angebot" (default) |
| offer.referenceNumber | "YYYY-MMDD-XXX" pattern |
| offer.date | German format: "25. Mai 2026" |
| offer.validUntil | Default: 30 days from today |
| body.intro, body.problem, body.solution | One paragraph each. Use du form unless the user is clearly a stranger / large enterprise. |
| body.deliverables | Array of HTML strings — <strong> allowed |
| body.sections[] | Optional extra sections like "Vorgehen", "Was wir brauchen" with body (intro line) + bullets |
| body.screenshots[] | Optional. { src: "assets/x.png", caption: "..." } — drop image files into assets/ first |
| angebot.items | Array of { position, title, description, qty, unitPrice }. unit is optional (omit it to show just the qty number). |
| angebot.paymentSummary | Optional short string (HTML) shown directly under the totals — good for "50 % bei Auftragserteilung…" |
| angebot.recurring | Optional { label, amount, per, description } |
| terms.paymentTerms | One paragraph describing the payment schedule |
| terms.extra | Optional raw HTML — use multiple <p> tags here for clean page breaks |
For provider, the canonical psquared defaults are:
"provider": {
"name": "psquared GmbH",
"contactPerson": "Martin Pammesberger",
"address": "Dametzstraße 2-4, 4020 Linz, Österreich",
"email": "[email protected]",
"website": "psquared.dev",
"jurisdiction": "Linz"
}
(Both Martin and Manuel are Geschäftsführer and can sign — pick one as contactPerson for the title page and signature line.)
Write the gathered config to a temp file (e.g. /tmp/create-offer-<slug>.json) and proceed.
cd /Users/<you>/Documents/psquared/psquared-skills/skills/create-offer
node build.mjs <path-to-config.json> <output.pdf>
The build does:
margin: 0 → edge-to-edge title PDF.margin: 40/25/30/25 mm, displayHeaderFooter: true → body PDF with brand footer + Seite X / Y.pdf-lib and write the output.If build.mjs errors out:
bash setup.sh.{{foo.bar}} shows up in the PDF → the field is missing from the JSON. Fill it in or remove the template reference.After build.mjs finishes, always sanity-check the PDF visually. Common things to look for:
break-after: avoid CSS.screenshot, .callout, .totals, .recurring, .payment-summary use break-inside: avoidopen "<output.pdf>" # macOS visual check
mdls -name kMDItemNumberOfPages "<output.pdf>"
Announce:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Offer PDF created. Client: [client.name] Title: [offer.title] Reference: [offer.referenceNumber or n/a] Total (brutto): [angebot.totalDisplay] Recurring: [angebot.recurring.amountDisplay / per] (if any) PDF: [absolute path] Pages: [count] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
skills/create-offer/
├── SKILL.md # this file
├── README.md # gotchas + extension notes
├── package.json # pins playwright + pdf-lib
├── setup.sh # idempotent installer
├── build.mjs # two-pass renderer + pdf-lib merge
├── .gitignore # node_modules, output.pdf, *.log
├── templates/
│ ├── style.css # shared psquared branding + print CSS
│ ├── title.html # edge-to-edge title page
│ ├── body.html # project description + screenshots
│ ├── angebot.html # pricing table + recurring + payment summary
│ └── terms.html # AGB sections + signature block
├── examples/
│ └── sanacom-example.json # canonical reference — copy this as a starting template
└── assets/ # drop screenshots / logo.svg here
du-form in body copy (intro, problem, solution, deliverables). Only switch to Sie if the client is clearly formal (large enterprise, unknown contact).hasDiscount, subtotalDisplay, totalDisplay, vatAmountDisplay are computed by build.mjs. Don't put them in the input JSON.assets/. Reference as "src": "assets/foo.png". The <base> tag in build.mjs ensures the relative path resolves.<p> tags inside terms.extra if you have multiple paragraphs — Chrome can then break cleanly between them. Don't use <br/><br/>.templates/style.css has a comment next to the .brand placeholder. If a real psquared logo SVG is dropped at assets/logo.svg, swap the .brand element in each template to <img src="assets/logo.svg" alt="psquared" class="brand-img" />.tools
Set up a personalized InboxMate INBOX demo (Demo-Postfach) for a sales prospect: a public, read-only seeded inbox showing 5-7 pre-triaged emails in their industry's language, with categories, routing and ready AI drafts. Use for email-automation outreach (the €49-349 product), NOT for chatbot outreach. No agent is created.
development
Build InboxMate demos AND write personalised outreach drafts in a single pass per company — eliminating the double-research that happens when /inboxmate-batch-demo and /setup-email-drafts run separately. Use when kicking off a new campaign where the campaign already exists (plan via /plan-campaign first). For each target company, dispatches ONE subagent that researches the site, builds the demo, creates the CRM opportunity, and drafts the outreach email — reusing the same research across all three. After all subagents return, runs a single batch call to auto-generate follow-ups.
testing
Autonomous pilot for the InboxMate EMAIL outreach (Demo-Postfach/INBOX track). Assesses where the inbox pipeline stands (leads → demos → review → campaign → drafts) and executes the next sensible step end-to-end, always finishing with the inbox sanity check and a summary of what the user should do next (ideally: just schedule the mails). Runs in save mode by default: orchestration + all quality gates on the top model, data collection on haiku subagents, content generation on sonnet subagents (pass 'full' to disable). Use when asked to 'advance the email outreach', 'run the inbox pipeline', or 'what's next for the Demo-Postfach motion'.
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.