proposal-maker

SkillWeb & browsing

Turn a raw commercial offer (client + line items + total) into an HTML proposal whose style copies a brand website. LLM-authored from a brand screenshot; --quick offline fallback. Output: proposal.html with clickable links + exact prices, prints to PDF. Use when: 'make a proposal', 'commercial offer', 'КП', 'коммерческое предложение'.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the proposal-maker skill

What this skill tells your AI

The instructions your AI receives, as published by mikefluff/skills in skills/proposal-maker/SKILL.md and read by ahel’s review.

The default is LLM-authored, screenshot-driven. A deterministic template can't capture "dark, bold, dramatic brand with a green accent and uppercase display type" — it reads colours by frequency and gets the mood wrong. So instead: a Python step assembles a brand kit, and you (the orchestrator) look at the brand and write the markup.

Execute-layer modules under common/runners/:

  1. proposal/parse.py — offer text → structured skills.proposal.plan.v1 (items, prices, recomputed subtotal, mismatch + outlier flags).
  2. proposal/brand.py — scrape brand tokens (accent / fonts / logo / name) + enrich each item with its real catalogue photo + description (og: tags).
  3. proposal/kit.py — screenshot the brand site (headless Chrome), download the logo, write BRIEF.md bundling tokens + the item table + authoring rules.
  4. proposal/render.py — the --quick deterministic themed template + HTML→PDF helper.

Why a document and NOT an image deck: a proposal carries exact prices and clickable product links. AI image generation garbles both. You write real HTML text.

This skill does NOT: generate slides/images (use carousel-builder/flyer-maker); invent prices or discounts; silently rewrite a suspicious number (it flags, you ask); log into a CRM or send anything; compute tax/VAT.

ROLE

Build the brand kit → look at the brand screenshot → surface data-quality warnings to the user → author a bespoke proposal.html that mirrors the brand → verify it by screenshotting your own output → deliver. Fall back to --quick only when there's no LLM loop or no network.

PIPELINE (default — LLM-authored)

  1. Capture the offer. Save the user's pasted offer verbatim to a temp file (or pipe via stdin). Don't reformat — the parser handles emoji headers, RU/EN keys, Name (url) qty — price CUR lines, and Total:.

  2. Build the brand kit:

    python3 proposal-maker/scripts/run.py --offer /tmp/offer.txt
    # brand site auto-detected from the offer footer; or pass --brand-url <site>
    

    This writes to ./generated/proposal/<slug>/: site.png (the brand screenshot), logo.*, brand.json, offer.json (enriched with per-item photo URLs), and BRIEF.md.

  3. Look at the brand. Read site.png — actually view it. Note: dark vs light mood, type weight/case, where/how the logo sits, accent colour usage, imagery feel. Then read BRIEF.md for the tokens + the full item table (names, qty, prices, links, photo URLs).

  4. Surface warnings. If the run printed ⚠ stated total ≠ computed or ⚠ '<item>' is N% of the total — possible typo, tell the user and ask before using that number. (The seed Double D offer has Логистика 5 000 000 — 97% of the total — almost certainly 5 000.) Never auto-fix; confirm, then correct the offer text and re-run if needed.

  5. Author proposal.html into the kit folder. House rules:

    • Mirror the screenshot. Same mood (dark canvas if the site is dark), same type personality (the brand font from BRIEF.md via its Google Fonts link), the accent as the one hot colour, the logo placed like the site places it.
    • Logo treatment. Brand logos are often monochrome/white SVGs. On a dark header use as-is; on a light header tint to white via filter:brightness(0) invert(1) or set it on a dark/accent plate. Always show the brand name beside it so identity survives.
    • Prominent key facts. Lead the hero with a big Date / Time / Location block — the fields the client checks first — above smaller secondary pills (guests, phone).
    • Group by category. Split the items into 4–7 logical categories named for the client's domain (e.g. Звук и свет / Доп оборудование / Артисты / Декорации / Сервис), each with a large, scannable header (big type + accent marker), item count, and per-category subtotal. Never one long pile.
    • Vary density. Showpiece / high-value items → big photo cards; utility / low-cost items (controllers, stands, staff, logistics) → compact 2-column rows (small thumb + name + price). Don't render everything large — it reads as sprawling.
    • Real photos. Each item gets its og:image from BRIEF.md (Tilda URLs hotlink fine). Items the kit could not photograph from a link get an auto-generated on-brand image (kit fills these; marked in BRIEF.md) — use it and tell the user they can swap a real one. Never a blank/placeholder if a photo can be sourced.
    • Exact data. Prices and links exactly as parsed; the computed subtotal is the total; quantity chips where given.
    • Self-contained. One file: inline <style>, the Google Fonts <link>, CSS custom properties for the tokens.
    • Print CSS (required — the PDF depends on it). @page{size:A4;margin:0} to kill white margins (full-bleed). Running header and footer on every page via the table <thead>/<tfoot> pattern (display:table-header-group/-footer-group in print) — a position:fixed band can't reserve per-page space and looks broken on inner pages. break-inside:avoid on cards/total. BRIEF.md ships the exact recipe.
  6. Verify your output. Screenshot the HTML you wrote and look at it:

    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --disable-gpu \
      --hide-scrollbars --window-size=1100,3600 --screenshot=/tmp/check.png \
      "file://$PWD/generated/proposal/<slug>/proposal.html"
    

    Read /tmp/check.png. Iterate until it genuinely reads as the brand. (proposal/kit exposes find_browser() for the binary path across platforms.)

  7. PDF (optional). Render the authored HTML to PDF via the system browser (no extra deps — keeps clickable links + the dark background):

    python3 proposal-maker/scripts/run.py --pdf-from generated/proposal/<slug>/proposal.html
    # → proposal.pdf next to it
    

    No headless browser? Tell the user to open proposal.html and Cmd/Ctrl+P → Save as PDF.

  8. Deliver. Print the paths (proposal.html + proposal.pdf).

--quick (deterministic, offline / no-LLM)

Renders one of three CSS themes (editorial / invoice / dark) from proposal/render.py — no screenshot, no authoring. Use when offline, when there's no LLM loop, or for a fast draft. Themes + auto-pick: see references/templates.md.

python3 proposal-maker/scripts/run.py --offer /tmp/offer.txt --quick --template dark --pdf

MODES

Required

  • --offer <path|-> or --offer-text "<…>".

Brand (overrides stack on top)

  • --brand-url <site> (else auto-detected from the offer footer) · --brand-file <brand.json> · --no-brand (defaults, no network) · --accent <#hex> · --font "<Family>" · --logo <url|path> · --brand-name "<Name>".

Saved brand profiles (brands/)

  • Reusable profiles live in brands/_index.md — each brands/<slug>/ holds a corrected brand.json + an authored template.html to clone + cached assets + reuse README.
  • When a profile exists for the client's brand, PREFER --brand-file brands/<slug>/brand.json over a fresh scrape: profiles encode manual corrections a live scrape gets wrong (dark themes read as light on Tilda/Webflow, vanishing white SVG logos).
  • After any client-ready proposal for a NEW brand, offer to save it as a profile (see the checklist in brands/_index.md).

Build / look

  • (default) brand-kit mode · --quick deterministic template · --template auto|editorial|invoice|dark (quick only) · --lang auto|ru|en · --no-thumbnails · --embed-images (quick) · --currency <CODE>.

Output / inspection

  • --pdf (quick mode: render PDF via the system browser) · --pdf-from <html> (render any HTML file to PDF and exit — the default-mode PDF step) · --output <dir> · --parse-only (parsed JSON, no network — best first dry-run) · --check (deps + --brand-url reachability).

REFERENCES (load on demand)

FileWhen
BRIEF.md (generated per run)The live authoring brief — tokens, item table, rules. Read it every run.
references/offer-format.mdParse contract — header keys, item grammar, qty vs line-total, total/outlier policy.
references/brand-extraction.mdWhat's scraped + manual overrides + the screenshot/kit step.
references/templates.mdThe 3 --quick themes + auto-pick.
references/troubleshoot.mdFonts/photos/logo/PDF/encoding issues.

EXAMPLES

See examples/before-after.md — the Double D Project event offer: the deterministic template (the wrong, light read) vs. the LLM-authored, brand-faithful dark proposal, plus the logistics-outlier handling.

CONSTRAINTS

  • Look before you build. In default mode you MUST view site.png before authoring. Don't trust brand.json.is_dark over your own eyes — the colour heuristic misreads dark Tilda/Webflow sites as light.
  • Copying the site's style is the whole point. Match its mood, type, accent, logo.
  • Exact prices + clickable links. Never render them as generated images.
  • Recompute, never silently rewrite. Show the computed subtotal; flag a disagreeing stated total and any line ≥60% of the total; ask the user.
  • Verify by screenshot. Author → screenshot → look → iterate. Don't ship unseen.
  • Enrichment is best-effort. A dead catalogue link leaves that item text-only.
  • Output is ./generated/proposal/<slug>/; slug from client name + event + date.
  • No secrets. Pure public-web scraping; no API keys.

INVOCATION HINTS

Trigger when the user says: "make a proposal / commercial offer / quote", "turn this offer into a nice document", "КП", "коммерческое предложение", "оффер для клиента", "смету покрасивее", "proposal in the style of ", "копируй стиль с сайта"; or pastes a Telegram/WhatsApp offer with 📆/👤/🧾/💰 fields + a price list.

Default: build the kit, view the screenshot, author the HTML. Recommend --parse-only first if the offer formatting looks unusual. Reach for --quick only when offline or when no authoring loop is available.

Distinct from flyer-maker/carousel-builder/cover-maker (those generate IMAGES) and landing-copy/cold-email (those write prose).

Signals

GitHub stars
21
Forks
1
Last commit
Sep 2026

ahel review

  • K6low
    bundled executables the agent is told to run
  • K1binfo
    installs-packages (in references/troubleshoot.md)

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Item type
skill
Key
proposal-maker
Source
github.com/mikefluff/skills