[ SALES & OPS ]
/sales-doc
Generate a sales offer, invoice, or contract for a ACME Agency prospect/client as a Google Doc, store it in the correct 2026 Drive folder (Ponude / Računi / Ugovori), and post the link to the #your-channel Slack channel.
ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install./sales-doc — offer, invoice, contract generator
Purpose
Sales closers at ACME Agency need to produce one of three Google Docs fast, right after a call:
- Ponuda (offer) — proposal with services + pricing
- Račun (invoice) — monthly billing with EUR total + BAM conversion
- Ugovor (contract) — business-cooperation contract ("Ugovor o poslovnoj saradnji")
Every doc is based on a tokenized master template on Drive. The skill copies the template, replaces {{PLACEHOLDERS}}, optionally deletes unused table rows, publishes the doc, and posts the link to #your-channel. The closer then opens the doc, fine-tunes anything specific, and downloads as PDF from the Docs UI.
When to trigger
- User says: "create offer for X", "napravi ponudu za X", "make an offer for X"
- User says: "create invoice for X", "napravi račun za X", "make invoice for X"
- User says: "create contract for X", "napravi ugovor za X"
- User invokes
/sales-docdirectly
Do not trigger for: general doc writing, ClickUp tasks, copywriting, or anything not in the three modes above.
Three modes
Mode A — Offer (--mode offer)
The offer renders from a single shell template (offer_shell) whose body is assembled at runtime from the module catalog in service_modules.json. The only fixed parts of every offer are the header, the pricing table, and the footer — everything in between is dynamically composed from the --modules list. No manual post-edits needed.
What you extract from the brief:
--client "Display Name"— appears in the header, e.g. "AGS Group d.o.o."--service-tag "..."— short label on the right side of the header. Auto-derives from modules if omitted.--modules comma,separated— pick from the module catalog below.--module-params "mod.key=value,mod.key=value"— per-module parameters (e.g. inbox count, prospect count for cold outbound).--monthly N— monthly retainer in EUR--setup N— one-time setup fee in EUR--budget-min N— recommended minimum ad budget, only shown for paid-ad offers (default 700)--date DD.MM.YYYY.— doc date (default today, Croatian format with trailing dot)--sender faris|adi— signature block--tier lead_engine|growth|scale|partner— productized package shortcut (see below). Auto-fills--monthly,--setup,--budget-min, and--modulesfromtiers.json, and injects the bounded scope + cadence. Any explicit flag still overrides the tier default.
Productized tiers (--tier)
The four standard packages live in .claude/skills/sales-doc/tiers.json (single source of truth — edit numbers there). Each tier defines bounded deliverables (e.g. "do 4 vizuala mjesečno", "0 video oglasa") and a communication cadence (channel / calls per month / response SLA / reporting). This is what stops scope creep: the offer states a number, not "daily management".
--tier | HR aliases | Monthly | Setup | Min budget | Modules |
|---|---|---|---|---|---|
lead_engine | pokretač, engine | 690 | 390 | 500 | meta_ads, crm (1 channel) |
growth | rast | 1.090 | 590 | 1.000 | + landing, email, tracking |
scale | skaliranje | 1.790 | 890 | 2.000 | both channels + funnel |
partner | dominacija | 3.900 | 1.500 | 4.000 | + social media + newsletters (3 slots) |
When --tier is passed (or auto-detected from price — see below), the offer renders a {{PACKAGE_BLOCK}} section (OPSEG PAKETA + KOMUNIKACIJA + DODATNE USLUGE add-on price list) before the price table, and the contract renders Član 4 — OPSEG USLUGA I DODATNE USLUGE ({{PACKAGE_ANNEX}}: bounded scope + comms cadence + out-of-scope clause + add-on cjenovnik) right after the price article. Both fill from tiers.json. The offer block is empty on non-tier offers; the contract's Član 4 falls back to a "scope is defined in the offer" sentence so the article is never empty. The à-la-carte add-on list also lives in tiers.json for the "recharge when they ask for too much" mechanism.
Template placeholders are inserted by node ACME Agency/scripts/<id>.mjs (idempotent; --revert removes them). The contract's scope was promoted from an end-of-doc annex to a numbered Član 4 (articles renumbered Č4→Č11) by node ACME Agency/scripts/<id>.mjs (one-time, idempotent). Re-run either only if a template is recreated.
Auto-detect + pricing flags:
- Auto-detect package from price: if
--tieris omitted but--monthlyis within ~15% (or 150 EUR) of a package, the scope is added automatically — so a closer who just enters a price close to a package still gets the scope in the offer/contract. Pass--customto disable this for a genuinely bespoke deal. - Setup pricing: standard
setup = monthly(1×, full, no discount). A discount is discretionary — pass--setup-discount <pct>(e.g.50= half off to close today, also40/30) and it recomputes the setup. No flag = no discount.
Full reasoning + closer talk-track + the onboarding call SOP + the pitch brochure (HTML→PDF) live in ACME Agency/strategy/ and the internal Drive folder "ACME Agency — Agency Self / Paketi i Pozicioniranje 2026". Regenerate the strategy/one-pager/SOP docs with node ACME Agency/strategy/build_package_docs.mjs and the brochure with node ACME Agency/strategy/build_pitch_brochure.mjs.
Module catalog (service_modules.json):
| Module id | Offer section (numbered in doc) |
|---|---|
meta_ads | Performance marketing — Meta bullets |
google_ads | Performance marketing — Google bullets |
landing | Izrada prodajne stranice |
crm | Integracija sa CRM platformom i buking kalendarom (full) |
email | Email automatizacija (sales-pipeline triggers) |
tracking | Tracking setup — bullet flavor picked by which ad platforms are in modules |
cold_email_accounts | Kreiranje novih email računa — params: inbox_count (default 30) |
cold_email_list | Generisanje email liste — params: prospects_count (default 2.000) |
cold_email_campaign | Kreiranje cold email kampanje |
cold_crm | CRM za praćenje upita (short cold-outbound version) |
linkedin | Kreiranje Cold LinkedIn kampanje — becomes a bullet section, NOT a line-item price |
Legacy aliases: meta → meta_ads, google → google_ads (accepted for backwards-compat).
Section ordering:
- Paid-ads offer (
meta_ads/google_adspresent): performance marketing → landing → crm → email → tracking → linkedin - Cold-outbound offer (any
cold_email_*orcold_crmorlinkedinwithout paid ads):cold_email_accounts→cold_email_list→cold_email_campaign→cold_crm→linkedin
Mapping natural-language briefs → flags:
| Closer phrase | Modules |
|---|---|
| "Meta + Google, full stack" | meta_ads,google_ads,landing,crm,email,tracking |
| "Meta only" | meta_ads,landing,crm,email,tracking |
| "Google only" | google_ads,landing,crm,email,tracking |
| "Just ads, no landing page" | meta_ads,google_ads,tracking |
| "Cold outbound + LinkedIn" | cold_email_accounts,cold_email_list,cold_email_campaign,cold_crm,linkedin |
| "LinkedIn outbound only" | linkedin,cold_crm |
If the closer doesn't specify --service-tag, derive from modules:
- only meta →
Meta Ads - only google →
Google Ads - both →
Performance marketing - cold email →
Cold outbound - linkedin only →
LinkedIn Outbound
Mode B — Invoice (--mode invoice)
What you extract:
--client "Display Name"--items "Label1:amount,Label2:amount,..."— up to 3 items. Typical labels:Mjesečne usluge,Setup fee,Budžet,Budžet Njemačka,Budžet Hrvatska + Slovenija--date DD.MM.YYYY.— invoice date (default today)--invoice-number DDxxMM/YY— override auto-numbering (only if closer insists)--bf NNNNNN— override the auto BF. Default BF = the invoice number with the/YYstripped (e.g. invoice190304/26→ BF190304). Closer only passes this when accounting needs a specific value.
First-invoice rule: if the closer says "first invoice", "prvi račun", "onboarding invoice", or this is clearly the first billing cycle for a new client (same month as the offer signing) AND the offer had a setup fee, include a Setup fee line in --items with the same amount as the offer's setup. Example — closer says "first invoice for Cekić, 1100 monthly" after an offer with 1100 setup → --items "Mjesečne usluge:1100,Setup fee:1100". Subsequent monthly invoices omit setup.
Legal details (OIB, address) auto-resolve for Croatian clients. Skill fails fast with clear list of missing fields if they can't be resolved — closer either adds them to ACME Agency/clients/legal.json or passes override flags:
--legal-name "CLIENT d.o.o."(defaults to --client)--oib <id>--address-l1 "Ulica Broj 1"--address-l2 "10000 Zagreb, Hrvatska"
Examples:
| Closer phrase | Flags |
|---|---|
| "Invoice for ACME Agency, 1250 mjesečno, BF 4511" | --client "ACME Agency" --items "Mjesečne usluge:1250" --bf 4511 |
| "Račun za ACME Agency, 1000 mjesečno, budžet Njemačka 1750, budžet HR+SLO 300, BF 4933" | --client "ACME Agency" --items "Mjesečne usluge:1000,Budžet Njemačka:1750,Budžet Hrvatska + Slovenija:300" --bf 4933 |
| "Invoice Perfect Solution, 550 mjesečno + 550 setup + 600 budžet, BF 5349" | --client "Perfect Solution" --items "Mjesečne usluge:550,Setup fee:550,Budžet:600" --bf 5349 |
Mode C — Contract (--mode contract)
What you extract:
--client "Display Name"--monthly N— monthly EUR retainer--setup N— one-time setup fee EUR--tier lead_engine|growth|scale|partner— defaults--monthly/--setup/--servicesfromtiers.jsonand prints the recommended Opseg + Radovi-izvan-opsega clauses for the closer to paste into the contract (so scope is defined contractually).--services "..."— short Croatian summary for Član 3, e.g. "Meta Ads, Google Ads i landing page" or "social media management, Google Ads i cold outbound aktivnosti". Default: "social media management, Google Ads i Meta Ads aktivnosti" (or the tier's module note when--tieris passed).--contract-date DD.MM.YYYY.— (default today)--contract-city "Sarajevo"— place of signing (default Sarajevo)- Legal details: same auto-resolution + override flags as Mode B, plus:
--rep-name "Goran Leutar"--rep-role direktor|vlasnik|prokurista(default direktor)
If legal info is missing after auto-resolution, skill prints a clear message listing which fields are unresolved and exits — never writes a half-filled contract.
How the skill runs
- Parse flags (or natural-language brief → flags via Claude).
- Load
config.json; fail if template ID for the mode isnull(setup script needs to run first). - Look up client legal details (invoice + contract modes only).
- Compute derived values: total EUR, BAM conversion, invoice number, assembled services block.
- Copy template → destination folder with a dated title.
replaceAllTextfor every token.- Delete empty table rows via
{{__EMPTY__}}sentinel (invoice items, offer extra line). makePublic+ get URL.- Post one message to
#your-channelwith the link.
Setup
Before first use, run the template setup sequence (order matters — shell depends on offer_standard):
node ACME Agency/scripts/<id>.mjs
node ACME Agency/scripts/setup_sales_doc_shell.mjs
The first script creates a Sales templates/ subfolder in Klijenti on Drive and produces invoice + contract + original offer templates with {{TOKENS}}. The second script derives a single "shell" offer template from the standard offer (hardcoded services block collapsed into {{SERVICES_BLOCK}}, header service label to {{SERVICE_TAG}}, budget note to {{BUDGET_NOTE_BLOCK}}). Template IDs are written to .claude/skills/sales-doc/config.json.
Invocation examples
node .claude/skills/sales-doc/script.mjs --mode offer \
--client "AGS Group d.o.o." \
--modules meta_ads,google_ads,landing,crm,email,tracking \
--monthly 1000 --setup 1000 --budget-min 700
# Cold outbound + LinkedIn example (DBCI 2026-04-24)
node .claude/skills/sales-doc/script.mjs --mode offer \
--client "D-Business Consulting International d.o.o." \
--modules cold_email_accounts,cold_email_list,cold_email_campaign,cold_crm,linkedin \
--module-params "cold_email_accounts.inbox_count=30,cold_email_list.prospects_count=2.500" \
--monthly 1600 --setup 1600
node .claude/skills/sales-doc/script.mjs --mode invoice \
--client "ACME Agency d.o.o." \
--items "Mjesečne usluge:1000,Budžet Njemačka:1750,Budžet Hrvatska + Slovenija:300" \
--bf 4933
node .claude/skills/sales-doc/script.mjs --mode contract \
--client "NIKO d.o.o." --monthly 690 --setup 890 \
--services "Google Ads i landing page" --rep-name "Goran Leutar" --rep-role direktor
Output
Single Slack message in #your-channel:
*Nova ponuda — AGS Group d.o.o.*
Performance marketing | 1.000,00 EUR/mj + 1.000,00 EUR setup | budžet od 700 EUR/mj
Doc: https://docs.google.com/document/d/.../edit
Folder: Ponude 2026
Closer opens the Doc, fine-tunes, downloads as PDF, sends to prospect.
Branded PDF offer (the Paradox-styled deliverable)
The Doc above is the editable offer. For a send-ready, designed PDF in the Paradox house style (dark cinematic + lime accent, Clash Display / General Sans, the your-domain.example wordmark — same look as the pitch brochure), use the branded-PDF engine. This is the standard polished offering a closer hands a prospect.
Trigger this path when the closer says any of: "ponuda u našem stilu / u našem PDF-u / u Paradox stilu", "nice PDF", "branded offer", "make it look like the ACME Agency one", or just "PDF" alongside an offer brief. It is also the right default whenever the offer is going straight to the client rather than needing manual fine-tuning. When in doubt, produce both — the editable Doc (normal flow) and the branded PDF.
Engine: ACME Agency/scripts/build_offer_pdf.mjs (renderer: scripts/lib/offer_pdf.mjs). It is deterministic: renders the PDF, self-verifies it exists and is a real file (validateFiles, ≥ 8 KB), and uploads to the client's Drive folder.
Flow (what the skill does):
- Parse the closer's brief into a spec object (below). Reuse the SAME module ids as the Doc offer (
google_ads,linkedin,landing,crm,email,tracking, …) so the PDF breakdown matches the Doc wording verbatim — the engine expands them fromservice_modules.json. Use explicitservices[]only for bespoke bullets not in the catalog. - Write the spec to a JSON file (e.g.
ACME Agency/clients/ACME Agency/examples/<slug>.json— seeACME Agency-skaliranje.jsonfor a full worked example). - Run:
node ACME Agency/scripts/build_offer_pdf.mjs --spec <path.json>(folder resolves fromspec.drive_folder_id, or pass--client "<name>"to look it up inclients.json, or--folder <id>). Add--no-uploadto pACME Agencyw locally first. - Return the Drive link. Croatian copy rules apply (no em-dashes, native phrasing, diacritics intact).
Spec contract (all fields optional except client + pricing.monthly + one of services/modules):
| Field | Meaning |
|---|---|
client | "ACME Agency Group d.o.o." — header + footer + Drive lookup |
date | "29.06.2026." (Croatian format, trailing dot) |
eyebrow | small label above the title (e.g. "Nastavak saradnje"); default "Ponuda" |
title / title_accent | cover headline; title_accent renders in lime on its own line |
intro | 2-3 sentence cover paragraph (HTML <b> allowed) |
modules | array of catalog ids → auto-expanded to service cards |
services | array of { name, bullets:[], badge? } for custom blocks (appended after modules) |
pricing | { monthly, was?, setup?, setup_waived?, currency? } — was shows the old price struck; setup_waived:true → "0 €" + "bez setup naknade"; currency EUR/BAM |
notes | array of value-add / incentive / budget lines (the bordered boxes on the ponuda page; HTML <b> allowed) |
closing | closing paragraph above the signature |
signer | { name, role } (default Faris Biogradlić / Head of Growth, ACME Agency) |
filename | upload filename (no extension) |
drive_folder_id | target Drive folder (else --client / --folder) |
What the closer gives you → what he sold (→ modules/services), the price (→ pricing.monthly), the incentive (a discount → pricing.was + a notes line; free add-on → a badge on that service + a notes line; waived setup → setup_waived:true), and optionally a date (→ date). Everything else has sane defaults.
Example (a teammate's brief → spec → PDF):
"napravi ponudu za ACME Agency u našem stilu: google ads, smm, linkedin ads (gratis), optimizacija profila, 3 landinga, crm+tracking — 2000€/mj, bez setupa, prošla cijena 3000€"
→ build the spec (modules + the two LinkedIn blocks as services with a "Uključeno kao dodatna vrijednost" badge, pricing:{monthly:2000, was:3000, setup_waived:true}, a notes line for the free LinkedIn value) → node ACME Agency/scripts/build_offer_pdf.mjs --spec ACME Agency.json → branded PDF in ACME Agency's Drive folder. See [clients/ACME Agency/examples/ACME Agency-skaliranje.json](../../../ACME Agency/clients/ACME Agency/examples/ACME Agency-skaliranje.json).