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/:
proposal/parse.py — offer text → structured skills.proposal.plan.v1 (items, prices,
recomputed subtotal, mismatch + outlier flags).
proposal/brand.py — scrape brand tokens (accent / fonts / logo / name) + enrich each
item with its real catalogue photo + description (og: tags).
proposal/kit.py — screenshot the brand site (headless Chrome), download the logo,
write BRIEF.md bundling tokens + the item table + authoring rules.
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)
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:.
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.
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).
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.
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.
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.)
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.
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)
| File |
When |
BRIEF.md (generated per run) |
The live authoring brief — tokens, item table, rules. Read it every run. |
| references/offer-format.md |
Parse contract — header keys, item grammar, qty vs line-total, total/outlier policy. |
| references/brand-extraction.md |
What's scraped + manual overrides + the screenshot/kit step. |
| references/templates.md |
The 3 --quick themes + auto-pick. |
| references/troubleshoot.md |
Fonts/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).
1---2name: proposal-maker3description: 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', 'КП', 'коммерческое предложение'.4license: MIT5---67<objective>8End-to-end commercial-proposal generator. Input: a free-form offer (client block +9itemised order with prices + total, usually pasted from Telegram/WhatsApp) and a brand10site to copy the look from. Output: a single self-contained `proposal.html` that genuinely11**looks like that brand** and prints to a clean PDF.1213The default is **LLM-authored, screenshot-driven**. A deterministic template can't capture14"dark, bold, dramatic brand with a green accent and uppercase display type" — it reads15colours by frequency and gets the mood wrong. So instead: a Python step assembles a16**brand kit**, and you (the orchestrator) *look at the brand* and *write the markup*.1718Execute-layer modules under `common/runners/`:191. `proposal/parse.py` — offer text → structured `skills.proposal.plan.v1` (items, prices,20 recomputed subtotal, mismatch + outlier flags).212. `proposal/brand.py` — scrape brand tokens (accent / fonts / logo / name) + enrich each22 item with its real catalogue photo + description (`og:` tags).233. `proposal/kit.py` — screenshot the brand site (headless Chrome), download the logo,24 write `BRIEF.md` bundling tokens + the item table + authoring rules.254. `proposal/render.py` — the `--quick` deterministic themed template + HTML→PDF helper.2627Why a document and NOT an image deck: a proposal carries **exact prices** and **clickable28product links**. AI image generation garbles both. You write real HTML text.2930This skill does NOT: generate slides/images (use `carousel-builder`/`flyer-maker`); invent31prices or discounts; silently rewrite a suspicious number (it flags, you ask); log into a32CRM or send anything; compute tax/VAT.33</objective>3435## ROLE3637Build the brand kit → **look at the brand screenshot** → surface data-quality warnings to38the user → **author a bespoke `proposal.html` that mirrors the brand** → verify it by39screenshotting your own output → deliver. Fall back to `--quick` only when there's no LLM40loop or no network.4142## PIPELINE (default — LLM-authored)43441. **Capture the offer.** Save the user's pasted offer verbatim to a temp file (or pipe via45 stdin). Don't reformat — the parser handles emoji headers, RU/EN keys, `Name (url) qty —46 price CUR` lines, and `Total:`.47482. **Build the brand kit:**49 ```bash50 python3 proposal-maker/scripts/run.py --offer /tmp/offer.txt51 # brand site auto-detected from the offer footer; or pass --brand-url <site>52 ```53 This writes to `./generated/proposal/<slug>/`: `site.png` (the brand screenshot),54 `logo.*`, `brand.json`, `offer.json` (enriched with per-item photo URLs), and55 `BRIEF.md`.56573. **Look at the brand.** `Read` `site.png` — actually view it. Note: dark vs light mood,58 type weight/case, where/how the logo sits, accent colour usage, imagery feel. Then read59 `BRIEF.md` for the tokens + the full item table (names, qty, prices, links, photo URLs).60614. **Surface warnings.** If the run printed `⚠ stated total ≠ computed` or `⚠ '<item>' is62 N% of the total — possible typo`, **tell the user and ask** before using that number.63 (The seed Double D offer has `Логистика 5 000 000` — 97% of the total — almost certainly64 `5 000`.) Never auto-fix; confirm, then correct the offer text and re-run if needed.65665. **Author `proposal.html`** into the kit folder. House rules:67 - **Mirror the screenshot.** Same mood (dark canvas if the site is dark), same type68 personality (the brand font from `BRIEF.md` via its Google Fonts link), the accent as69 the one hot colour, the logo placed like the site places it.70 - **Logo treatment.** Brand logos are often monochrome/white SVGs. On a dark header use71 as-is; on a light header tint to white via `filter:brightness(0) invert(1)` or set it72 on a dark/accent plate. **Always show the brand name beside it** so identity survives.73 - **Prominent key facts.** Lead the hero with a big Date / Time / Location block — the74 fields the client checks first — above smaller secondary pills (guests, phone).75 - **Group by category.** Split the items into 4–7 logical categories named for the76 client's domain (e.g. Звук и свет / Доп оборудование / Артисты / Декорации / Сервис),77 each with a **large, scannable header** (big type + accent marker), item count, and78 **per-category subtotal**. Never one long pile.79 - **Vary density.** Showpiece / high-value items → big photo cards; utility / low-cost80 items (controllers, stands, staff, logistics) → compact 2-column rows (small thumb +81 name + price). Don't render everything large — it reads as sprawling.82 - **Real photos.** Each item gets its `og:image` from `BRIEF.md` (Tilda URLs hotlink83 fine). Items the kit could not photograph from a link get an **auto-generated on-brand84 image** (kit fills these; marked in `BRIEF.md`) — use it and tell the user they can85 swap a real one. Never a blank/placeholder if a photo can be sourced.86 - **Exact data.** Prices and links exactly as parsed; the computed subtotal is the total;87 quantity chips where given.88 - **Self-contained.** One file: inline `<style>`, the Google Fonts `<link>`, CSS custom89 properties for the tokens.90 - **Print CSS (required — the PDF depends on it).** `@page{size:A4;margin:0}` to kill91 white margins (full-bleed). Running header **and** footer on every page via the92 **table `<thead>`/`<tfoot>`** pattern (`display:table-header-group`/`-footer-group` in93 print) — a `position:fixed` band can't reserve per-page space and looks broken on94 inner pages. `break-inside:avoid` on cards/total. `BRIEF.md` ships the exact recipe.95966. **Verify your output.** Screenshot the HTML you wrote and look at it:97 ```bash98 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --disable-gpu \99 --hide-scrollbars --window-size=1100,3600 --screenshot=/tmp/check.png \100 "file://$PWD/generated/proposal/<slug>/proposal.html"101 ```102 `Read` `/tmp/check.png`. Iterate until it genuinely reads as the brand. (`proposal/kit`103 exposes `find_browser()` for the binary path across platforms.)1041057. **PDF (optional).** Render the authored HTML to PDF via the system browser (no extra106 deps — keeps clickable links + the dark background):107 ```bash108 python3 proposal-maker/scripts/run.py --pdf-from generated/proposal/<slug>/proposal.html109 # → proposal.pdf next to it110 ```111 No headless browser? Tell the user to open `proposal.html` and Cmd/Ctrl+P → Save as PDF.1121138. **Deliver.** Print the paths (`proposal.html` + `proposal.pdf`).114115## --quick (deterministic, offline / no-LLM)116117Renders one of three CSS themes (editorial / invoice / dark) from `proposal/render.py` —118no screenshot, no authoring. Use when offline, when there's no LLM loop, or for a fast119draft. Themes + auto-pick: see `references/templates.md`.120121```bash122python3 proposal-maker/scripts/run.py --offer /tmp/offer.txt --quick --template dark --pdf123```124125## MODES126127### Required128- `--offer <path|->` or `--offer-text "<…>"`.129130### Brand (overrides stack on top)131- `--brand-url <site>` (else auto-detected from the offer footer) · `--brand-file <brand.json>` ·132 `--no-brand` (defaults, no network) · `--accent <#hex>` · `--font "<Family>"` ·133 `--logo <url|path>` · `--brand-name "<Name>"`.134135### Saved brand profiles (`brands/`)136- Reusable profiles live in [`brands/_index.md`](brands/_index.md) — each `brands/<slug>/` holds a corrected `brand.json` + an authored `template.html` to clone + cached assets + reuse README.137- 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).138- After any client-ready proposal for a NEW brand, offer to save it as a profile (see the checklist in `brands/_index.md`).139140### Build / look141- (default) brand-kit mode · `--quick` deterministic template · `--template auto|editorial|invoice|dark` (quick only) ·142 `--lang auto|ru|en` · `--no-thumbnails` · `--embed-images` (quick) · `--currency <CODE>`.143144### Output / inspection145- `--pdf` (quick mode: render PDF via the system browser) · `--pdf-from <html>` (render any146 HTML file to PDF and exit — the default-mode PDF step) · `--output <dir>` ·147 `--parse-only` (parsed JSON, no network — best first dry-run) · `--check` (deps +148 `--brand-url` reachability).149150## REFERENCES (load on demand)151152| File | When |153|---|---|154| `BRIEF.md` (generated per run) | The live authoring brief — tokens, item table, rules. Read it every run. |155| [references/offer-format.md](references/offer-format.md) | Parse contract — header keys, item grammar, qty vs line-total, total/outlier policy. |156| [references/brand-extraction.md](references/brand-extraction.md) | What's scraped + manual overrides + the screenshot/kit step. |157| [references/templates.md](references/templates.md) | The 3 `--quick` themes + auto-pick. |158| [references/troubleshoot.md](references/troubleshoot.md) | Fonts/photos/logo/PDF/encoding issues. |159160## EXAMPLES161162See [examples/before-after.md](examples/before-after.md) — the Double D Project event offer:163the deterministic template (the wrong, light read) vs. the LLM-authored, brand-faithful164dark proposal, plus the logistics-outlier handling.165166## CONSTRAINTS167168- **Look before you build.** In default mode you MUST view `site.png` before authoring.169 Don't trust `brand.json.is_dark` over your own eyes — the colour heuristic misreads170 dark Tilda/Webflow sites as light.171- **Copying the site's style is the whole point.** Match its mood, type, accent, logo.172- **Exact prices + clickable links.** Never render them as generated images.173- **Recompute, never silently rewrite.** Show the computed subtotal; flag a disagreeing174 stated total and any line ≥60% of the total; ask the user.175- **Verify by screenshot.** Author → screenshot → look → iterate. Don't ship unseen.176- **Enrichment is best-effort.** A dead catalogue link leaves that item text-only.177- **Output is `./generated/proposal/<slug>/`**; slug from client name + event + date.178- **No secrets.** Pure public-web scraping; no API keys.179180## INVOCATION HINTS181182Trigger when the user says: "make a proposal / commercial offer / quote", "turn this offer183into a nice document", "КП", "коммерческое предложение", "оффер для клиента", "смету184покрасивее", "proposal in the style of <site>", "копируй стиль с сайта"; or pastes a185Telegram/WhatsApp offer with 📆/👤/🧾/💰 fields + a price list.186187Default: build the kit, view the screenshot, author the HTML. Recommend `--parse-only`188first if the offer formatting looks unusual. Reach for `--quick` only when offline or when189no authoring loop is available.190191Distinct from `flyer-maker`/`carousel-builder`/`cover-maker` (those generate IMAGES) and192`landing-copy`/`cold-email` (those write prose).