Loading...
Loading...
Configure-Price-Quote (CPQ) vertical skill for the Customware SPA. Defines the section layout, config schema, business rule templates, and deterministic mapping rules for transforming a DOMAIN.md into a CPQ config object. Use this skill when the Builder Agent classifies a customer's domain as a quoting, pricing, or product configuration system. Trigger signals: products with dependencies, price lists, markup/margin calculations, quote generation, proposal workflows, accessory compatibility, product configuration options.
npx skill4agent add customware-ai/skills cpq-builder{
"sections": [
{
"id": "configure",
"label": "Configure",
"icon": "Settings2",
"order": 1,
"component": "card-selector",
"componentConfig": {
"itemLayout": "grid",
"showPrice": true,
"showDescription": true,
"showOptions": true,
"selectionMode": "add-to-list",
"groupBy": "category"
},
"dataSource": "data.products",
"actions": [
{ "label": "Add to Quote", "action": "addSelected", "variant": "primary" }
]
},
{
"id": "quote",
"label": "Build Quote",
"icon": "FileText",
"order": 2,
"component": "data-table",
"componentConfig": {
"columns": [
{ "key": "product", "label": "Product", "width": "auto" },
{ "key": "options", "label": "Configuration", "width": "auto" },
{ "key": "quantity", "label": "Qty", "width": "80px", "editable": true },
{ "key": "unitPrice", "label": "Unit Price", "width": "120px", "format": "currency" },
{ "key": "total", "label": "Total", "width": "120px", "format": "currency", "computed": true }
],
"showRowActions": true,
"rowActions": ["edit", "duplicate", "remove"]
},
"dataSource": "data.lineItems",
"actions": [
{ "label": "Preview Quote", "action": "navigateTo:preview", "variant": "primary" },
{ "label": "Clear All", "action": "clearItems", "variant": "ghost", "confirm": true }
]
},
{
"id": "preview",
"label": "Preview",
"icon": "Eye",
"order": 3,
"component": "summary",
"componentConfig": {
"showLineItems": true,
"showSubtotal": true,
"showMarkup": false,
"showTax": true,
"showTotal": true,
"showTerms": true,
"showNotes": true
},
"dataSource": "data.lineItems",
"actions": [
{ "label": "Export PDF", "action": "exportPDF", "variant": "primary" },
{ "label": "Back to Quote", "action": "navigateTo:quote", "variant": "ghost" }
]
},
{
"id": "approve",
"label": "Approve",
"icon": "CheckCircle",
"order": 4,
"component": "form",
"componentConfig": {
"fields": [
{ "key": "customerName", "label": "Customer Name", "type": "text", "required": true },
{ "key": "customerEmail", "label": "Customer Email", "type": "email" },
{ "key": "paymentTerms", "label": "Payment Terms", "type": "select", "default": "net30" },
{ "key": "notes", "label": "Notes", "type": "textarea" },
{ "key": "validUntil", "label": "Valid Until", "type": "date" }
]
},
"dataSource": "data.quoteSettings",
"gated": {
"requires": ["customerName", "hasLineItems", "noErrors"],
"message": "Complete these before approving: customer name, at least one product, no unresolved dependency errors."
},
"actions": [
{ "label": "Approve & Send", "action": "approveQuote", "variant": "primary", "confirm": true },
{ "label": "Save Draft", "action": "saveDraft", "variant": "secondary" }
]
}
],
"navMode": "stepper"
}stepperreferences/config-schema.mdconfig
├── app ← Branding, theme (from brandfetch)
├── sections[] ← Fixed (from this skill, above)
├── data
│ ├── products[] ← Catalog of available products with options
│ ├── lineItems[] ← Current quote contents (starts empty)
│ └── quoteSettings ← Currency, tax, terms, markup
├── rules[] ← Business rules (dependencies, validations)
└── roles[] ← User roles and permissionsFOR EACH entity in DOMAIN.md Entity Registry
WHERE entity description suggests a sellable product, equipment, service, or part:
→ CREATE config.data.products[] entry
→ SET id = slugify(entity name)
→ SET name = entity name (exact, from DOMAIN.md)
→ SET category = entity parent grouping or category (from Entity Registry or Relationship Map)
→ SET basePrice = entity price if stated (number, no currency symbol)
→ SET pricingSource = determine from context:
IF "price list" or "vendor list" or "catalog price" → "catalog"
IF "get a quote from vendor" or "depends on specs" → "vendor_rfq"
IF "we know our cost and mark it up" → "cost_plus"
IF no pricing discussed → "tbd"
→ SET options[] = from entity's "what varies" attributes:
FOR EACH variation mentioned (size, model, capacity, material, type):
→ CREATE option entry
→ SET option.id = slugify(variation name)
→ SET option.label = variation name
→ SET option.type = "select" (default for enumerated choices)
→ SET option.choices = the specific values mentioned
WHERE entity has NO price, NO options, and is NOT referenced in a relationship:
→ SKIP — it's probably not a product (may be a role, process, or system)
→ ADD to Open Questions: "Is [entity] a quotable product?"FOR EACH relationship in DOMAIN.md Relationship Map:
IF relationship type = "requires":
→ CREATE config.rules[] entry
→ SET type = "requires"
→ SET severity = "error"
→ SET trigger = "addItem"
→ SET condition = { "item.category": [source entity category] }
→ SET action = { "suggest": [target entity id] }
→ IF relationship mentions size/model matching:
→ SET action.matchField = the matching attribute
→ SET message = relationship rationale from DOMAIN.md (the "because")
→ IF no rationale captured:
→ SET message = "[source] requires [target] — rationale not captured"
IF relationship type = "recommends":
→ CREATE config.rules[] entry
→ SET type = "recommends"
→ SET severity = "warning"
→ SET trigger = "addItem"
→ SET condition = { "item.category": [source entity category] }
→ SET action = { "suggest": [target entity id] }
→ SET message = relationship rationale from DOMAIN.md
IF relationship type = "excludes":
→ CREATE config.rules[] entry
→ SET type = "excludes"
→ SET severity = "error"
→ SET trigger = "addItem"
→ SET condition = { "item.category": [source entity category] }
→ SET action = { "block": [target entity id] }
→ SET message = relationship rationale from DOMAIN.mdFOR EACH rule in DOMAIN.md Business Rules:
IF rule mentions "approval" or "requires authorization":
→ CREATE config.rules[] entry
→ SET type = "validates"
→ SET trigger = the action being gated (e.g., "setTerms", "approveQuote")
→ SET condition = the triggering condition
→ SET action = { "requireApproval": [role] }
→ SET severity = "warning"
→ SET message = rule rationale
IF rule mentions "cannot" or "must not" or "not allowed":
→ CREATE config.rules[] entry
→ SET type = "validates"
→ SET trigger = the blocked action
→ SET condition = the triggering condition
→ SET action = { "block": true }
→ SET severity = "error"
→ SET message = rule rationale
IF rule mentions pricing constraint (markup, margin, discount limit):
→ CREATE config.rules[] entry
→ SET type = "computes"
→ SET trigger = "priceCalculation"
→ SET condition = the pricing formula or constraint
→ SET action = { "compute": [formula description] }
→ SET message = rule rationaleIF DOMAIN.md State Models contains payment terms or approval statuses:
→ MAP to config.data.quoteSettings.availableTerms[]
→ SET config.data.quoteSettings.defaultTerms = the default mentioned
IF DOMAIN.md State Models contains quote statuses:
→ MAP to section gating logic
→ The CPQ skill handles this through the fixed section definitions (Configure → Quote → Preview → Approve)→ SET config.app.companyName = DOMAIN.md Project Overview company name
→ SET config.app.theme.primaryColor = from brandfetch (or fallback "#1a1a2e")
→ SET config.app.theme.accentColor = from brandfetch (or fallback "#3b82f6")
→ SET config.app.theme.logoUrl = from brandfetch
→ SET config.app.theme.mode = "light"→ SET config.data.quoteSettings.currency = from DOMAIN.md (or default "USD")
→ SET config.data.quoteSettings.taxEnabled = true/false based on DOMAIN.md
→ SET config.data.quoteSettings.taxLabel = from DOMAIN.md (e.g., "HST", "GST", "Sales Tax")
→ SET config.data.quoteSettings.taxRate = from DOMAIN.md (decimal, e.g., 0.13 for 13%)
→ SET config.data.quoteSettings.defaultTerms = from DOMAIN.md (e.g., "net30")
→ SET config.data.quoteSettings.markup = from DOMAIN.md (decimal, e.g., 0.375 for 37.5%)
→ SET config.data.quoteSettings.quoteFormat = "itemized" (default, unless DOMAIN.md says otherwise)FOR EACH role in DOMAIN.md User Roles:
→ CREATE config.roles[] entry
→ SET id = slugify(role name)
→ SET label = role name (exact, from DOMAIN.md)
→ SET permissions = infer from DOMAIN.md role description:
IF role creates quotes → ["createQuote", "editQuote", "selectProducts"]
IF role approves → ["approveQuote", "approveTerms", "viewReports"]
IF role manages catalog → ["editCatalog", "editPricing"]
IF role is view-only → ["viewQuotes"]
→ IF permissions cannot be inferred → SET permissions = ["createQuote", "editQuote"] (safe default)→ IF entity has no price → SET pricingSource = "tbd", ADD to config metadata openQuestions
→ IF relationship rationale is missing → SET message = "[source] [relationship] [target] — rationale not captured"
→ IF entity cannot be classified as a product → SKIP, ADD to config metadata openQuestions
→ IF DOMAIN.md has Open Questions → COPY to config.metadata.openQuestions for reference
→ IF no roles are defined in DOMAIN.md → CREATE one default role: { id: "user", label: "User", permissions: ["createQuote", "editQuote", "selectProducts"] }
→ IF no pricing information at all → SET quoteSettings.markup = 0, quoteSettings.currency = "USD", ADD note to openQuestions{
"id": "BR-XXX",
"type": "requires",
"trigger": "addItem",
"condition": { "item.category": "[source_category]" },
"action": { "suggest": "[target_product_id]", "matchField": "[matching_attribute]" },
"message": "[rationale from DOMAIN.md]",
"severity": "error"
}{
"id": "BR-XXX",
"type": "recommends",
"trigger": "addItem",
"condition": { "item.category": "[source_category]" },
"action": { "suggest": "[target_product_id]" },
"message": "[rationale from DOMAIN.md]",
"severity": "warning"
}{
"id": "BR-XXX",
"type": "excludes",
"trigger": "addItem",
"condition": { "item.category": "[source_category]" },
"action": { "block": "[target_product_id]" },
"message": "[rationale from DOMAIN.md]",
"severity": "error"
}{
"id": "BR-XXX",
"type": "validates",
"trigger": "[gated_action]",
"condition": { "[field]": { "$ne": "[default_value]" } },
"action": { "requireApproval": "[role_id]" },
"message": "[rationale from DOMAIN.md]",
"severity": "warning"
}{
"id": "BR-XXX",
"type": "computes",
"trigger": "priceCalculation",
"condition": { "item.pricingSource": "cost_plus" },
"action": { "compute": "sellingPrice = cost * (1 + markup)" },
"message": "Cost-plus pricing: [markup]% markup applied",
"severity": "info"
}references/vertical-presets.md| Vertical | Products Are | Pricing Model | Key Dependency Pattern | Quote Style |
|---|---|---|---|---|
| Manufacturing / BOM | Fabricated equipment, assemblies | Cost-plus or vendor RFQ | Equipment requires accessories, parts, installation | Itemized with scope of work |
| Wholesale / Distribution | Catalog items, bulk goods | Price list with volume tiers | Product bundles, case/pallet quantities | Itemized with quantity breaks |
| Services / Equipment Integrator | Equipment + installation + PM | Mixed (catalog + vendor RFQ + labor rates) | Equipment requires service, service includes consumables | Itemized with service schedule |
## Mapping Log
**Skill:** cpq-builder v3.0
**DOMAIN.md:** [company name]
**Vertical preset:** [manufacturing / wholesale / services / none]
### Products mapped: [N]
- [entity name] → config.data.products[0] ([pricingSource], [N] options)
- [entity name] → config.data.products[1] ([pricingSource], [N] options)
### Rules mapped: [N]
- BR-001: [source] requires [target] → severity: error
- BR-002: [source] recommends [target] → severity: warning
### Skipped entities: [N]
- [entity name] — not a quotable product (added to openQuestions)
### Open questions carried forward: [N]
- [question from DOMAIN.md]references/config-schema.mdreferences/vertical-presets.mdreferences/example-config.json