@elitedcs/ghl-mcp 3.70.0 → 3.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +8 -5
  3. package/dist/capture-helper.js +10 -1
  4. package/dist/index.js +7479 -1552
  5. package/guide/guide.html +2 -1
  6. package/package.json +2 -2
  7. package/skills/blueprint/SKILL.md +2 -0
  8. package/skills/blueprint/examples/medspa-approval-view.md +58 -53
  9. package/skills/blueprint/examples/medspa-brief.json +70 -8
  10. package/skills/blueprint/examples/medspa-build-plan.json +1435 -123
  11. package/skills/blueprint/examples/medspa-dry-run-report.md +2 -0
  12. package/skills/blueprint/examples/sample-approval-view.md +20 -61
  13. package/skills/blueprint/examples/sample-brief.json +95 -7
  14. package/skills/blueprint/examples/sample-build-plan.json +1448 -119
  15. package/skills/blueprint/examples/validate-plan.cjs +195 -13
  16. package/skills/blueprint/presets/clinic-launch-a2p.preset.json +1 -0
  17. package/skills/blueprint/presets/clinic.md +60 -0
  18. package/skills/blueprint/presets/clinic.preset.json +1737 -0
  19. package/skills/blueprint/presets/coach.md +58 -0
  20. package/skills/blueprint/presets/coach.preset.json +1723 -0
  21. package/skills/blueprint/presets/ecommerce.md +54 -0
  22. package/skills/blueprint/presets/ecommerce.preset.json +1287 -0
  23. package/skills/blueprint/presets/generic-client.md +49 -27
  24. package/skills/blueprint/presets/generic-client.preset.json +1552 -122
  25. package/skills/blueprint/presets/local-service.md +58 -0
  26. package/skills/blueprint/presets/local-service.preset.json +1733 -0
  27. package/skills/blueprint/presets/med-spa.md +47 -48
  28. package/skills/blueprint/presets/med-spa.preset.json +1557 -111
  29. package/skills/blueprint/references/brief-schema.md +48 -1
  30. package/skills/blueprint/references/build-plan-schema.md +66 -5
  31. package/skills/blueprint/references/copy-guide.md +167 -0
  32. package/skills/blueprint/references/intake-question-set.md +64 -3
  33. package/skills/blueprint/references/preset-format.md +97 -51
  34. package/templates/action-schemas.json +12 -0
@@ -36,10 +36,57 @@ The Brief is the normalized business profile the plan-gen skill consumes. The MC
36
36
  | `channels.payment` | enum | Stripe connected / Stripe not connected / other / none |
37
37
  | `channels.calendarConnected` | bool | |
38
38
  | `channels.social` | array | |
39
+ | `channels.hasPhoneNumber` | enum | `yes` \| `no` \| `unsure` — "Do you already have a phone number in GoHighLevel?" *(v2)* |
40
+ | `team.staff` | array | `[{name, email, role?, mobile?}]` — EVERY staff member to set up; becomes plan `users[]` *(v2)* |
41
+ | `team.notifyName` | string | who is notified about new leads: a staff name or "the owner" *(v2)* |
42
+ | `team.callsName` | string | who takes booking / follow-up calls: a staff name or "the owner" *(v2)* |
43
+ | `calendars` | array | `[{name, type, staffNames, durationMinutes?}]`, `type` ∈ `one_on_one` \| `round_robin` \| `class` — one entry per booking calendar the client asked for *(v2)*; `durationMinutes` = the appointment length when the answer gave one ("45 minutes" → 45), which the plan carries as `calendars[].slotDuration` *(finding 25)* |
44
+ | `voice.threeWords` | string | brand voice in three words — the copywriter's tone brief *(v2)* |
45
+ | `voice.signatureLine` | string | a line the client always says to new clients, verbatim — reused in the first email / text *(v2)* |
39
46
  | `assets.existingPipeline` | string | |
40
47
  | `assets.existingWorkflows` | string | do-not-clobber list |
41
48
  | `assets.brand` | string | |
42
49
  | `assets.notes` | string | |
43
- | `flags` | array | derived: `needs_a2p`, `stripe_not_connected`, `calendar_oauth_needed`, `email_domain_needed` |
50
+ | `flags` | array | derived: `needs_a2p`, `stripe_not_connected`, `calendar_oauth_needed`, `email_domain_needed`, `phone_number_needed` *(v2: SMS wanted and no confirmed number)* |
51
+ | `warnings` | array | parse-time notes from the normalizer (a staff line it could not read, a calendar type it assumed). Never fatal. *(v2)* |
44
52
 
45
53
  A canonical worked example is in `examples/sample-brief.json`. Omit unknown fields; never emit `null`.
54
+
55
+ ## v2 additions (2026-08-26) — the pieces a good build needs
56
+
57
+ The owner inspected a real build: it had created one user and one calendar because the intake never asked who the staff are or how many calendars they need. `team`, `calendars`, `voice` and `channels.hasPhoneNumber` carry that. All optional — a 0.1 brief still validates.
58
+
59
+ Worked example (the v2 block only):
60
+ ```jsonc
61
+ {
62
+ "team": {
63
+ "staff": [
64
+ { "name": "Jane Smith", "email": "jane@glow.com", "role": "Front desk", "mobile": "555-123-4567" },
65
+ { "name": "Dr. Mark Lee", "email": "mark@glow.com", "role": "Provider" }
66
+ ],
67
+ "notifyName": "Jane Smith",
68
+ "callsName": "the owner"
69
+ },
70
+ "calendars": [
71
+ { "name": "New Patient Consult", "type": "round_robin", "staffNames": ["Jane Smith", "Dr. Mark Lee"], "durationMinutes": 45 },
72
+ { "name": "Follow-up Call", "type": "one_on_one", "staffNames": ["Jane Smith"], "durationMinutes": 15 }
73
+ ],
74
+ "channels": { "sms": true, "hasPhoneNumber": "no" },
75
+ "voice": { "threeWords": "warm, direct, unhurried", "signatureLine": "You will never look overdone here." },
76
+ "flags": ["needs_a2p", "phone_number_needed"]
77
+ }
78
+ ```
79
+
80
+ How it lands in the plan: each `team.staff[]` entry → a `users[].ref` (`user.jane_smith`); `notifyName` → the `userRef` on the new-lead notification; `callsName` → the task assignee / `assign_user`; each `calendars[]` entry → a `calendars[]` object with `calendarType` (`one_on_one` → `event`, `round_robin` → `round_robin`, `class` → `class_booking`; *finding 36*: a `one_on_one` that NAMES someone becomes `round_robin` instead, because `event` calendars cannot hold a team member — GoHighLevel accepts them and drops the person), `teamMemberRefs` and `slotDuration` (from `durationMinutes`; omitted → GoHighLevel's 30-minute default); `voice` → the tone and the reused line in every template. No staff listed → those steps use `user.__pending__` and are reported as waiting.
81
+
82
+ ### `validate_brief` — what it returns
83
+ `{valid, errors, warnings, brief}`. `errors` are schema failures (blocking). `warnings` are the gaps a schema-valid brief can still carry — advisory, never fatal; the skill asks or marks the step as waiting:
84
+
85
+ | Warning | When |
86
+ |---|---|
87
+ | No staff listed — notifications will be marked as waiting for a staff member | `team.staff` empty or absent |
88
+ | Calendar "X" names no staff | a `calendars[]` entry with empty `staffNames` |
89
+ | Calendar "X" lists "Y", who is not in the staff list | a name on a calendar that is not in `team.staff` (owner aliases accepted) |
90
+ | Booking is wanted but no calendar was described | `goal.bookingNeeded` (or a "book…" primary goal) and no `calendars[]` |
91
+ | "Y" (notified about new leads / taking calls) is not in the staff list | `notifyName` / `callsName` not a staff member nor "the owner" |
92
+ | *(passthrough)* | every entry of `brief.warnings` from the normalizer |
@@ -13,7 +13,7 @@ The Build Plan is the reviewable, editable output of the skill. **Refs only, no
13
13
  4. **Verify-before-continue** is inherited by the executor.
14
14
 
15
15
  ### Ref grammar
16
- `<objectType>.<slug>` (lowercase snake_case, unique within type). Namespaces: `pipeline` `stage` `field` `tag` `workflow` `form` `funnel` `page` `calendar` `email` `sms` `cv` `handoff`.
16
+ `<objectType>.<slug>` (lowercase snake_case, unique within type). Namespaces: `pipeline` `stage` `field` `tag` `workflow` `form` `funnel` `page` `calendar` `email` `sms` `cv` `handoff` and, since v2 (2026-08-26): `user` `email_template` `sms_template`. One literal is allowed where a `user.*` ref belongs: **`user.__pending__`** = "no staff member yet — build the step, report it as waiting for a staff member".
17
17
 
18
18
  ## Top level
19
19
  ```jsonc
@@ -23,30 +23,91 @@ The Build Plan is the reviewable, editable output of the skill. **Refs only, no
23
23
  "briefId": "sub_...",
24
24
  "preset": "generic",
25
25
  "summary": "Plain-English description for the approver. Record preset id+version + brief source here.",
26
+ "users": [...], // v2 — every staff member from the brief
26
27
  "pipelines": [...], "customFields": [...], "tags": [...], "customValues": [...],
27
28
  "calendars": [...], "forms": [...], "funnels": [...],
28
- "emails": [...], "sms": [...], "workflows": [...], "handoffs": [...],
29
- "buildOrder": ["tag.*","field.*","cv.*","pipeline.*","calendar.*","form.*","funnel.*","email.*","sms.*","workflow.*"],
29
+ "emails": [...], "sms": [...],
30
+ "templates": { "emails": [...], "sms": [...] }, // v2 — every message lives once
31
+ "workflows": [...], "handoffs": [...],
32
+ "buildOrder": ["user.*","tag.*","field.*","cv.*","pipeline.*","calendar.*","form.*","funnel.*","email.*","sms.*","email_template.*","sms_template.*","workflow.*"],
30
33
  "idMap": {}
31
34
  }
32
35
  ```
33
36
  `buildOrder` is advisory; the executor derives true order from dependencies. `idMap` is empty at authoring; the executor fills `ref → realId`.
34
37
 
35
38
  ## Object shapes (abbrev — full examples in `examples/sample-build-plan.json`)
39
+ - **users** *(v2)*: `{ref, firstName, lastName, email, role, phone?}` — one per `brief.team.staff[]` entry; `role` ∈ admin/user (the owner and whoever runs the account = admin; everyone else = user); `email` must be a real address (GHL creates the login from it) and unique across users. Every step that pings a person points at a user by `userRef`; the plan never carries a real user id.
36
40
  - **pipelines:** `{ref, name, stages:[{ref, name, position}]}`
37
41
  - **customFields:** `{ref, name, dataType, model?, options?}` — `dataType` ∈ TEXT/LARGE_TEXT/NUMERICAL/PHONE/**MONETORY**/CHECKBOX/SINGLE_OPTIONS/MULTIPLE_OPTIONS/FLOAT/DATE/TEXTBOX_LIST/FILE_UPLOAD/SIGNATURE; `model` ∈ contact/opportunity; choice types (SINGLE_OPTIONS/MULTIPLE_OPTIONS/CHECKBOX) MUST carry `options:[...]`
38
42
  - **tags:** `{ref, name}`
39
43
  - **customValues:** `{ref, name, value, filledBy?}` — `value` may be blank when produced by a handoff
40
- - **calendars:** `{ref, name, calendarType, openHours, availabilityType, requiresStaff}` — `calendarType` ∈ round_robin/event/class_booking/collective/service_booking
44
+ - **calendars:** `{ref, name, calendarType, openHours, availabilityType, requiresStaff, teamMemberRefs?, slotDuration?, slotDurationUnit?, slotInterval?, slotBuffer?}` — `calendarType` ∈ round_robin/event/class_booking/collective/service_booking. One calendar per `brief.calendars[]` entry (brief `one_on_one` → `event`, `round_robin` → `round_robin`, `class` → `class_booking`); `teamMemberRefs` *(v2)* = the `user.*` refs on it (a staffed calendar with none → warning, and it waits for a manual assignment). **`slotDuration`** = the appointment length in minutes (`slotDurationUnit` "mins", default; "hours" allowed), carried from `brief.calendars[].durationMinutes` / the calendar answer ("Discovery Call, 15 minutes" → `slotDuration: 15`). Omitted → GoHighLevel builds **30-minute** slots and the validator warns; on a re-run that binds an existing calendar with a different length, the build reports the mismatch as a manual step and never changes the calendar itself. `slotInterval` (minutes between start times) and `slotBuffer` (minutes after each appointment) are optional pass-throughs.
41
45
  - **forms:** `{ref, name, fields:[{type:"standard"|"custom", key?|fieldRef?, required}]}`
42
46
  - **funnels:** `{ref, name, target?, host?, domain?, pages:[{ref, name, role, outline, formRef?, calendarRef?}]}` — `target` ∈ ghl (default) / external; `host` ∈ cloudflare (default) / vercel (external only); `domain` external only, optional (else host subdomain). All three are additive + optional → a plan with no `target` builds in GHL (today's behavior); `schemaVersion` stays 0.1. When `target: "external"` the funnel takes the external lane (`references/external-funnel.md`): the site is generated + user-hosted, not built in GHL.
43
47
  - **emails:** `{ref, name, subject?, bodyOutline?, body?, mergeTags?}` — an email SENT by a workflow needs a full `body` (an outline-only asset reports `needsContent` and will not build)
44
48
  - **sms:** `{ref, name, bodyOutline?, body?, mergeTags?}` — same `body` rule for any SMS a workflow sends
49
+ - **templates** *(v2)*: `{emails:[{ref: "email_template.<slug>", name, subject, html}], sms:[{ref: "sms_template.<slug>", name, body}]}` — every message lives ONCE as an account-level email template / SMS snippet the client can edit in GHL without opening a workflow. Templates are created in the editor's own format (vibe-editor) so they open and edit in Marketing → Emails → Templates, and each saved body is read back from GHL's preview before it is reported as written. Content is FULL (`html` / `body`), never an outline. A send step references one with `templateRef`; the executor creates the template AND inlines the same body into the step. Names unique per kind (bound by name). Write the copy in the client's voice: `brief.voice.threeWords` sets the tone and `brief.voice.signatureLine` is reused verbatim in the first email / text.
45
50
  - **workflows:** `{ref, name, trigger?, stopOnResponse?, actions:[logical actions, refs not IDs]}` — max 40 actions; split longer flows. `trigger.type` ∈ the executor's native set so it auto-builds: `contact_tag` (tagRef), `form_submission` (formRef), `appointment` (+ `appointmentStatus`: confirmed/noshow/new/showed/cancelled/invalid; calendarRef optional), `customer_reply`, `pipeline_stage_updated` (pipelineRef+stageRef), `inbound_webhook`, `payment_received`. `form_submitted`/`contact_replied` are aliases; `tag_added`/`appointment_status`/`appointment_booked` are NOT recognized → manual step.
46
51
  - **handoffs:** `{ref, owner, title, trigger?, instruction, produces?, successCheck, blocks?}` — `owner` ∈ **OPERATOR-UI/OPERATOR-EXT/TEAM** (legacy JERRY-UI/JERRY-EXT/SASHA accepted + normalized, never emitted). To gate a workflow DRAFT, `blocks` must list its `workflow.*` ref (a bare `sms.*` wildcard gates only the asset surface).
47
52
 
48
53
  ## Logical workflow actions (executor expands to native)
49
- `add_contact_tag {tagRef}`, `remove_contact_tag {tagRef}`, `send_email {emailRef}`, `send_sms {smsRef}`, `wait {value, unit}`, `wait_appointment {value, unit}` (integer; appointment-triggered workflows only), `internal_notification {to, title, body}` (`to` = a real GHL user id), `task_notification {title, body?, dueDate?, assignedTo?}`, `update_contact_field {fieldRef, value}`, `add_notes {body}`, `create_opportunity {pipelineRef, stageRef, name?, value?}`, `update_opportunity {pipelineRef, stageRef, value?}` (forces allowBackward), `add_to_workflow {workflowRef}`, `remove_from_workflow {workflowRef}`, `goal_event {goalCondition, action?}`, and `find_opportunity {pipelineRef, found:[...], notFound:[...]}` — the only branching action; it MUST be the LAST action (branches do not rejoin). All pointers are refs. The executor owns the failure-prone native shapes; the plan never contains them.
54
+ `add_contact_tag {tagRef}`, `remove_contact_tag {tagRef}`, `send_email {emailRef | templateRef}`, `send_sms {smsRef | templateRef}` (one of the two is required — `templateRef` points at `templates.*`, v2), `wait {value, unit}`, `wait_appointment {value, unit}` (integer; appointment-triggered workflows only), `internal_notification {userRef, title, body}` (`userRef` = a `user.*` ref or `user.__pending__`; the 0.1 `to` literal is still accepted with a warning), `task_notification {title, body?, dueDate?, userRef?}` (`assignedTo` literal still accepted with a warning; no assignee = warning), `assign_user {userRef}` *(v2 — GHL "Assign to user"; the contact's owner becomes that person)*, `update_contact_field {fieldRef, value}`, `add_notes {body}`, `create_opportunity {pipelineRef, stageRef, name?, value?}`, `update_opportunity {pipelineRef, stageRef, value?}` (forces allowBackward), `add_to_workflow {workflowRef}`, `remove_from_workflow {workflowRef}`, `goal_event {goalCondition, action?}`, and `find_opportunity {pipelineRef, found:[...], notFound:[...]}` — the only branching action; it MUST be the LAST action (branches do not rejoin). All pointers are refs. The executor owns the failure-prone native shapes; the plan never contains them.
55
+
56
+ ## v2 rules the validator enforces (2026-08-26) — read before writing workflows
57
+ Two are **hard errors on purpose** even for plans that use no new field. Both come from a real build the owner inspected: the nurture went quiet after a week and the speed-to-lead ended with a text and dropped the lead. Error strings start with the code.
58
+
59
+ | Code | Rule | What to write instead |
60
+ |---|---|---|
61
+ | `E_NURTURE_TOO_SHORT` | A workflow whose **name** contains "nurture" must span **≥ 30 days**: the sum of its `wait` steps (minutes/hours converted; `wait_appointment` not counted; a terminal `find_opportunity` adds its longer arm). The error reports the computed days. | Keep adding touches + waits until the waits add up to 30+ days (e.g. 1, 2, 4, 7, 7, 9 = 30). |
62
+ | `E_NO_HANDOFF` | A workflow whose **name** contains "speed" (speed-to-lead) must **end** with a hand-off: its **last** action is `add_to_workflow`, or `add_contact_tag` with a tag that some nurture-named workflow's trigger fires on. Tagging at the START does not count (the nurture would run in parallel with the first-touch texts). A terminal `find_opportunity` passes only when both arms end in a hand-off. | End with `{ "type": "add_contact_tag", "tagRef": "tag.nurture_start" }` and give the nurture `trigger: { "type": "contact_tag", "tagRef": "tag.nurture_start" }`. |
63
+ | `E_NO_USER_REF` | An `internal_notification` with neither `userRef` nor a legacy `to`. | Add `userRef` (a `users[]` ref) or `user.__pending__`. |
64
+ | `E_NO_MESSAGE_REF` | A `send_email` / `send_sms` with neither an asset ref nor a `templateRef`. | Point it at `templates.*` (preferred) or a 5.8 asset. |
65
+
66
+ Warnings (advisory): `user.__pending__` on a step → "WAITING FOR A STAFF MEMBER" (the approval view shows it); a legacy `to` / `assignedTo` literal → move to `userRef`; a task with no assignee; a staffed calendar with no `teamMemberRefs` when the plan declares users.
67
+
68
+ ### Worked example — users, calendars, templates, and the two rules together
69
+ ```jsonc
70
+ {
71
+ "users": [
72
+ { "ref": "user.jane_smith", "firstName": "Jane", "lastName": "Smith", "email": "jane@glow.com", "role": "admin", "phone": "+15551234567" },
73
+ { "ref": "user.mark_lee", "firstName": "Mark", "lastName": "Lee", "email": "mark@glow.com", "role": "user" }
74
+ ],
75
+ "tags": [{ "ref": "tag.hot_lead", "name": "hot-lead" }, { "ref": "tag.nurture_start", "name": "nurture-start" }],
76
+ "calendars": [{ "ref": "calendar.consult", "name": "New Patient Consult", "calendarType": "round_robin",
77
+ "slotDuration": 45, "slotDurationUnit": "mins",
78
+ "requiresStaff": true, "teamMemberRefs": ["user.jane_smith", "user.mark_lee"] }],
79
+ "templates": {
80
+ "emails": [{ "ref": "email_template.nurture_1", "name": "Nurture 1 — Value",
81
+ "subject": "The thing most people get wrong about aging skin",
82
+ "html": "<p>Hi {{contact.first_name}} — You will never look overdone here. …</p>" }],
83
+ "sms": [{ "ref": "sms_template.nurture_1", "name": "Nurture text 1",
84
+ "body": "Hi {{contact.first_name}}, still want that consult? Reply STOP to opt out." }]
85
+ },
86
+ "workflows": [
87
+ { "ref": "workflow.speed_to_lead", "name": "Speed to Lead",
88
+ "trigger": { "type": "form_submission", "formRef": "form.intake" }, "stopOnResponse": true,
89
+ "actions": [
90
+ { "type": "add_contact_tag", "tagRef": "tag.hot_lead" },
91
+ { "type": "create_opportunity", "pipelineRef": "pipeline.main", "stageRef": "stage.new_lead", "name": "{{contact.name}} - New Lead" },
92
+ { "type": "internal_notification", "userRef": "user.jane_smith", "title": "New lead", "body": "New inquiry from {{contact.first_name}}" },
93
+ { "type": "task_notification", "userRef": "user.jane_smith", "title": "Call {{contact.first_name}} within 5 minutes", "dueDate": "1" },
94
+ { "type": "assign_user", "userRef": "user.mark_lee" },
95
+ { "type": "wait", "value": 5, "unit": "minutes" },
96
+ { "type": "send_sms", "templateRef": "sms_template.nurture_1" },
97
+ { "type": "add_contact_tag", "tagRef": "tag.nurture_start" } // ← the hand-off: LAST
98
+ ] },
99
+ { "ref": "workflow.lead_nurture", "name": "Lead Nurture",
100
+ "trigger": { "type": "contact_tag", "tagRef": "tag.nurture_start" }, "stopOnResponse": true,
101
+ "actions": [
102
+ { "type": "wait", "value": 1, "unit": "days" }, { "type": "send_email", "templateRef": "email_template.nurture_1" },
103
+ { "type": "wait", "value": 6, "unit": "days" }, { "type": "send_sms", "templateRef": "sms_template.nurture_1" },
104
+ { "type": "wait", "value": 9, "unit": "days" }, { "type": "send_email", "templateRef": "email_template.nurture_1" },
105
+ { "type": "wait", "value": 14, "unit": "days" }, { "type": "add_contact_tag", "tagRef": "tag.hot_lead" }
106
+ ] } // waits: 1+6+9+14 = 30 days ✓
107
+ ]
108
+ }
109
+ ```
110
+ No staff listed in the brief? Keep the steps and write `"userRef": "user.__pending__"` — the build ships, the approval view says who is still needed.
50
111
 
51
112
  ## §5A approval view
52
113
  Rendered as two ordered checklists (auto-build vs manual handoffs). Spec + template in `references/approval-view.md`.
@@ -0,0 +1,167 @@
1
+ # Copy Guide — every email and text the Blueprint sends
2
+
3
+ STATUS: v1, 2026-08-26. Owner: Command OS. Read by the build stage before it writes a single template, and by anyone editing a preset.
4
+
5
+ Why this exists, in the owner's words after inspecting a real build: "If we provide everything generic, our users will not be impressed and will not stay with us long." The workflows are only as good as the words inside them. This guide is the standard every message is written to and rewritten to.
6
+
7
+ ## 0. The one rule
8
+
9
+ **Every message is a complete, send-ready template, written from the client's brief, that a real person would be glad to have signed.** Never an outline. Never a placeholder sentence. Never "insert benefit here." If a fact is missing from the brief, write the message without that fact; do not invent one.
10
+
11
+ ## 1. How copy flows through a build
12
+
13
+ 1. The preset ships **complete templates** (`templates.emails[]` with `subject` + `html`, `templates.sms[]` with `body`) written from placeholder brief fields. They are the floor, not the ceiling.
14
+ 2. The build stage **rewrites every template from the real brief**: `business.name`, `offer.summary`, `offer.leadMagnet`, `offer.pricePoints`, `audience.ideal`, `audience.painPoints`, `audience.objections`, `goal.followUpStyle`, `voice.threeWords` (tone), `voice.signatureLine` (verbatim in the instant email and text), and `extended.*` (Agency OS ICA / offer / brand DNA) when present.
15
+ 3. The rewrite **keeps**: the cadence (which day, which channel), the single CTA, the merge fields, the STOP lines, the length limits, and the compliance rules of the industry (clinic: nothing clinical in a message).
16
+ 4. The result is written to **both** the template (`html` / `body`) and the matching asset (`emails[].body` / `sms[].body`), and the send step references the template with `templateRef`. An asset with only `copyDirection` reports `needsContent` and will not build.
17
+ 5. Each template's `copyDirection` (on the asset) says what that message is for. Honor it; it encodes the sequence's logic (day 8 gives a tip, day 14 handles price, day 27 is the decision sheet).
18
+
19
+ ## 2. The shape of a message: Hook → Resonance → Belief → Action
20
+
21
+ Every email and every text, in this order, however short:
22
+
23
+ | Beat | What it does | Test |
24
+ |---|---|---|
25
+ | **Hook** | Earns the next three seconds. Names the reader's situation, not our product. | Would the first six words stop *this* person, and only this person? |
26
+ | **Resonance** | Shows we understand before we sell. Their words for the problem (from `audience.painPoints`), not ours. | Could they say "that's exactly it"? |
27
+ | **Belief** | Gives a reason to trust the promise: a mechanism ("we treat less first, then adjust"), a process ("here's the visit minute by minute"), or a real proof point from the brief. | Does the proof match the size of the claim? No proof, smaller claim. |
28
+ | **Action** | One ask. One. A reply, a tap, a word. | Could they do it with a thumb in under ten seconds? |
29
+
30
+ A text can do all four in two sentences. An email does it in 60 to 150 words.
31
+
32
+ ## 3. What the Gary V / Alex Hormozi standard means here: five concrete tests
33
+
34
+ Run every message through all five. A message that fails one gets rewritten, not shipped.
35
+
36
+ 1. **The Jab test (give before you ask).** In any three consecutive touches of a sequence, at least two give something with no ask beyond a reply: a tip they can use today, the answer to an objection they have not voiced yet, a decision aid ("three questions to ask anyone you hire"), a plain-words explanation of cost. The third may ask for the booking. A sequence that asks every time is spam with a nice font.
37
+ 2. **The Value Equation test (Hormozi).** Every message that asks for the next step raises the dream outcome and the likelihood, and lowers the time and the effort, in words: what they get ("a written plan you keep"), why it will work ("we check your benefits before you come in"), how fast ("30 minutes", "this week"), how easy ("reply with one word"). If a message lowers none of the four, it is not ready.
38
+ 3. **The Says-who test.** Every specific (a number, a result, a "most people", a "clients tell us") either traces to the brief or comes out. "We are passionate about quality" fails on the other side: it is a claim nobody can check and nobody believes. Specific and true, or gone. No fabricated stories, no invented client, no "one customer told me last week."
39
+ 4. **The Lock-screen test.** Read the SMS's first six words, or the email's subject plus first line, as a lock-screen preview. They must say who this is and why it matters to the reader *today*. "Just checking in" fails. "Dana at Glow: mornings or afternoons?" passes.
40
+ 5. **The Thumb test (make it easy to say yes).** The reply is one word, one tap, or one day of the week. The CTA is the only link. Nobody is asked to "let us know if you have any questions" or "feel free to reach out." Binary questions beat open ones: "mornings or afternoons?", "still on the list this season, or should I close your file?"
41
+
42
+ ## 4. Voice
43
+
44
+ - **One person to one person.** Every message is signed by a named human (`{{custom_values.owner_first_name}}`), addresses `{{contact.first_name}}`, and uses "I" and "you." No "we at [Business] are excited to."
45
+ - **Plain spoken English.** Contractions. Short sentences. The way the owner talks to a client at the front desk. If a sentence would sound odd said out loud, cut it.
46
+ - **Tone from the brief.** `voice.threeWords` sets it ("warm, direct, unhurried" reads differently from "bold, playful, fast"). `voice.signatureLine` goes in verbatim, as its own paragraph after the greeting of the instant email, and as the second sentence of the instant text when it fits.
47
+ - **Honest about being automated where it matters.** The last message of a sequence says it is the last automatic one. A "no pitch, just checking" text must contain no pitch.
48
+ - **No hype, no pressure words, no fake urgency.** A deadline exists only when the store or calendar actually enforces it (a code that expires, a calendar that fills). "Act now", "limited time", "don't miss out" never appear.
49
+ - **No em-dashes.** Use a period, a comma, a colon, or restructure.
50
+
51
+ ## 5. Email rules
52
+
53
+ | Rule | Detail |
54
+ |---|---|
55
+ | Subject | 45 characters or fewer (60 hard cap). Sentence case. No ALL CAPS, no exclamation marks, no "Re:" / "Fwd:" tricks, no spam words (free!!!, guaranteed, act now). The first name in the subject is allowed once per sequence. |
56
+ | First line | Doubles as preview text. Starts the hook; never "Hi {{contact.first_name}}, I hope this finds you well." |
57
+ | Length | 60 to 150 words. The decision-sheet email may reach 200 because it is a list. |
58
+ | Layout | Single column, 600px max, 16px Arial-family text, short paragraphs (1 to 3 sentences), a bulleted list where there are three or more parallel items. No hero images required; a text-first email lands and loads. |
59
+ | CTA | One. A button (`CTA:` paragraph in the preset source) or one text link. The same destination, never two different ones. The last nurture emails may add "or reply to this email" as the zero-effort alternative; that is the same ask, not a second one. |
60
+ | Signature | First name, business name, phone: `{{custom_values.owner_first_name}}` / `{{custom_values.business_name}}` / `{{custom_values.business_phone}}`. |
61
+ | Proof | Only from the brief. No invented testimonials, numbers, credentials, or anecdotes. Where a preset leaves room for proof and the brief has none, delete the sentence. |
62
+ | Exits | Every sequence offers a graceful out ("reply with the word later", "reply less"). GHL appends the unsubscribe footer; do not fake one. |
63
+
64
+ ## 6. SMS rules
65
+
66
+ | Rule | Detail |
67
+ |---|---|
68
+ | Length | 160 characters rendered is the target (one segment). 320 is the hard cap; the validator rejects longer. Measure with real values (a long business name counts). |
69
+ | Identify | The first text of every workflow names the business and, where it fits, the person. Later texts in the same workflow within seven days may skip it. |
70
+ | STOP line | "Reply STOP to opt out." on the first text of every workflow and on any text after a gap of seven days or more. Not on every text (it reads like a robot). |
71
+ | Links | No link in the instant text (deliverability, and it reads like spam). The booking link may appear from the second touch on, at most one per text, and never in a text whose job is a question. |
72
+ | The ask | Every text ends with one question or one single-word instruction: "Mornings or afternoons?", "Reply YES", "Reply with a day that works." Never two questions. |
73
+ | Cadence | Instant, then 20 minutes, then the next day in Speed to Lead. Never more than one text per day in a nurture. Never before 8am or after 8pm local (set the workflow's send window in GHL). |
74
+ | Merge fields | `{{contact.first_name}}`, `{{custom_values.business_name}}`, `{{custom_values.owner_first_name}}`, `{{custom_values.booking_link}}`, `{{custom_values.business_phone}}`, and `{{appointment.start_time}}` only inside appointment-triggered workflows. |
75
+ | Compliance | Nothing clinical, financial, or embarrassing in a text, ever (a text is read on a lock screen). A2P copy must match the registered use case; the intake's compliance notes win over this guide. |
76
+
77
+ ## 7. Merge fields and custom values
78
+
79
+ Use these, and only these, so the copy stays correct when details change and the executor can verify each one exists:
80
+
81
+ | Merge field | Meaning | Where it comes from |
82
+ |---|---|---|
83
+ | `{{contact.first_name}}` | the reader | GHL contact |
84
+ | `{{contact.phone}}`, `{{contact.email}}`, `{{contact.name}}` | internal notes, task bodies, notifications only | GHL contact |
85
+ | `{{custom_values.business_name}}` | the client's business name | `customValues` (from `business.name`) |
86
+ | `{{custom_values.business_phone}}` | the number to call or text back | `customValues` (filled once a GHL number exists) |
87
+ | `{{custom_values.owner_first_name}}` | who every message is signed by | `customValues` (the first name of `team.notifyName`'s user) |
88
+ | `{{custom_values.booking_link}}` | the calendar link | `customValues` (filled by the calendar handoff) |
89
+ | `{{custom_values.store_link}}`, `{{custom_values.welcome_code}}`, `{{custom_values.code_expiry_days}}` | ecommerce presets only | `customValues` |
90
+ | `{{appointment.start_time}}` | the booked time | GHL, only in workflows with an `appointment` trigger |
91
+
92
+ Preset tokens (`{{offer.leadMagnet || a free consultation}}`, `{{audience.painPoints[0] || ...}}`) are **not** merge fields: the fill step replaces them from the brief before anything is built. The rewrite should leave no preset token behind; every `{{...}}` in a built template must be a GHL merge field from the table.
93
+
94
+ ## 8. Banned openers and phrases
95
+
96
+ Never: "I hope this email finds you well", "Just checking in", "Just following up", "Touching base", "Per my last email", "Are you tired of…", "In today's fast-paced world", "We're excited to", "As a valued customer", "Don't miss out", "Limited time only", "Act now", "Hi there" (use the name), "Let us know if you have any questions", "Feel free to reach out", "We pride ourselves on", "We're passionate about", "Look no further", "Unlock", "Elevate", "Transform your", "Game-changer".
97
+
98
+ Replace with a specific: what happened, what we noticed, what we can do, what to reply.
99
+
100
+ ## 9. Cadence the presets ship (and the rewrite keeps)
101
+
102
+ | Sequence | Touches | Span | Shape |
103
+ |---|---|---|---|
104
+ | Speed to Lead | 5 (3 texts, 2 emails) | 24 hours | instant text + email, call task, +20 min text (binary question), +3 h email (what happens next), +1 day text (not chasing), then the `nurture-start` hand-off as the LAST action |
105
+ | Missed Call Text-Back | 2 texts | 1 hour | instant text-back (how can we help), tag, alert, call-back task, +1 h text (call-back time or booking link) |
106
+ | Lead Nurture (30 days) | 11 (6 emails, 5 texts) | 31 days of waits | day 1 reframe, 3 one-word question, 5 how it works + top objection, 8 free tip, 10 method as proof, 14 price/time head-on, 15 midpoint binary, 20 cost of waiting, 23 any question, 27 decision sheet, 30 last direct ask, then `lifecycle-lapsed` + `winback-start` |
107
+ | Win-back (30 days) | 5 (3 emails, 2 texts) | 31 days of waits | day 0 one question, 7 binary text, 14 what changed, 22 concrete reason text, 30 the door stays open (says it is the last automatic message), then `lifecycle-lost` |
108
+ | Appointment | confirm email + text; 24 h text + prep email; 2 h text | appointment-relative | confirmation restates time, what to bring, what they leave with, how to move it |
109
+ | No-Show Rescue | 4 (2 texts, 2 emails) | 7 days | same-day text + email (no guilt), +1 day text, +3 days email (still want this?), then hand-off to Win-back |
110
+
111
+ Total: 21 customer touches from form to lost, before the appointment sequences. That is the owner's "it takes sometimes 21 points of touch" standard.
112
+
113
+ ## 10. Per-message checklist
114
+
115
+ Before a template is written to the plan, every line must be true:
116
+
117
+ - [ ] Addresses `{{contact.first_name}}` and is signed by a named person.
118
+ - [ ] Hook → Resonance → Belief → Action, in that order, in the reader's language.
119
+ - [ ] Passes all five tests in section 3 (Jab, Value Equation, Says-who, Lock-screen, Thumb).
120
+ - [ ] One CTA. One question or one instruction to end a text.
121
+ - [ ] Every specific traces to the brief; nothing invented; proof gaps deleted, not filled.
122
+ - [ ] Subject 45 characters or fewer, sentence case, no spam words. SMS under 160 rendered (320 hard cap).
123
+ - [ ] STOP line where section 6 requires it; no link in an instant text.
124
+ - [ ] Only merge fields from section 7; no preset token left behind.
125
+ - [ ] Tone matches `voice.threeWords`; `voice.signatureLine` used where section 4 says.
126
+ - [ ] Industry compliance honored (clinic: nothing clinical; A2P: matches the registered use case).
127
+ - [ ] No banned phrase from section 8. No em-dashes.
128
+ - [ ] The `copyDirection` for this slot is honored (the day's job in the sequence).
129
+
130
+ ## 11. Worked email (Nurture 4, "About the price", local service)
131
+
132
+ Brief facts used: business "Ridgeline Roofing", owner "Marcus", offer "roof repair and replacement", objection "price", no proof points supplied (so none claimed).
133
+
134
+ ```
135
+ Subject: About the price
136
+
137
+ Hi {{contact.first_name}},
138
+
139
+ Let's talk about the thing most people are too polite to ask: what is this going to cost?
140
+
141
+ The honest answer is "it depends on what we find", which is why we don't guess over the phone. What we can promise: the quote is itemized, it shows the repair-only option next to the full-replacement option where both exist, and payment terms are on the same page. Nothing is added after the fact unless you change the job, and you'd sign for that first.
142
+
143
+ If you have a budget in mind, tell us at the estimate. It's easier to design the job to the budget than to argue about it after.
144
+
145
+ [ Get my written quote ] ← one button → {{custom_values.booking_link}}
146
+
147
+ Marcus
148
+ Ridgeline Roofing · {{custom_values.business_phone}}
149
+ ```
150
+
151
+ Why it passes: hook names the unspoken question (Lock-screen); "it depends" is honest and specific to the trade (Says-who: no invented number); the mechanism is the itemized side-by-side quote (Belief; Value Equation raises likelihood, lowers effort); "tell us your budget" is a jab, not an ask (Jab); one button (Thumb). 118 words.
152
+
153
+ ## 12. Worked SMS (Speed to Lead, instant text, local service)
154
+
155
+ ```
156
+ Hi {{contact.first_name}}, it's Marcus at Ridgeline Roofing. Got your quote request. Quick one: is this urgent today, or can it wait for a scheduled visit? Reply STOP to opt out.
157
+ ```
158
+
159
+ Rendered with a long name: 158 characters. Why it passes: identifies the person and the business in the first six words (Lock-screen); asks the only question that changes what happens next, as a binary (Thumb); no link in the first text; STOP line present (first text of the workflow). The reply routes the lead: "urgent" gets a call now, anything else gets the +20 minute photo text.
160
+
161
+ ## 13. What to do when the brief is thin
162
+
163
+ - No pain points or objections: keep the preset's industry defaults (they are written for the vertical) and say so in the plan summary.
164
+ - No lead magnet: the CTA is the conversation ("book a time to talk"), never a made-up freebie.
165
+ - No staff names: sign with `{{custom_values.owner_first_name}}` and leave the custom value for `handoff.business_details`; never invent a name.
166
+ - No proof: delete the proof sentence. A shorter true email beats a longer invented one.
167
+ - Clinic with compliance notes: the notes win. Remove anything they forbid, even if this guide would otherwise allow it.
@@ -1,10 +1,12 @@
1
- # Intake Question Set — v1.1 (hand-off to ghl-command-mcp)
1
+ # Intake Question Set — v1.2 (hand-off to ghl-command-mcp)
2
2
 
3
- STATUS: FINAL for v1.1, 2026-06-15. Owner: atlas (wording/labels) → ghl-command-mcp (builds the form-template installer from this). Jerry approves the final set. **v1.1 adds 3 keys** (`team_size`, `monthly_lead_volume`, `business_hours`) per Jerry's 2026-06-15 ruling — see "Ratified additions" at the bottom.
3
+ STATUS: FINAL for v1.2, 2026-08-26 (question-set version `0.2`). Owner: atlas (wording/labels) → ghl-command-mcp (builds the form-template installer from this). Jerry approves the final set. **v1.1 added 3 keys** (`team_size`, `monthly_lead_volume`, `business_hours`) per Jerry's 2026-06-15 ruling. **v1.2 adds 7 keys in three new sections** G "Your team", H "Calendars and phone", I "Your voice" — after the owner inspected a real build (2026-08-26): *"Our intake is not sufficient and missing pieces that prevent a good build. The better we do upfront, the less we have to go in and modify later."* See "Tier 1 v2 additions" at the bottom.
4
4
 
5
5
  This is the canonical list of questions the installed intake form asks. It is the **built-in fallback brief source** (schema §2A path B) — the path taken when no partner OS (Agency OS) is detected or the subscriber declines it. It is the FLOOR: the form is installed regardless, because it is the only path when no partner OS is present.
6
6
 
7
- **Contract rule honored:** the 25 original `key`s below are unchanged from schema §3 (changing a key is a contract change). Only wording/labels/options/help text are finalized here. **3 keys were ADDED** (`team_size`, `monthly_lead_volume`, `business_hours`) per Jerry's 2026-06-15 ruling — a coordinated contract change: command-center folds them into schema §3 + §6, ghl-command-mcp adds them to the installer. They are integrated into the sections below (Section A: A6–A8) and the Key Brief map; the atlas-owned dataType + brief-path decisions are documented under "Ratified additions" at the bottom.
7
+ **Contract rule honored:** the 25 original `key`s below are unchanged from schema §3 (changing a key is a contract change). Only wording/labels/options/help text are finalized here. **3 keys were ADDED** (`team_size`, `monthly_lead_volume`, `business_hours`) per Jerry's 2026-06-15 ruling and **7 more on 2026-08-26** (`staff_members`, `notify_name`, `calls_name`, `booking_calendars`, `has_phone_number`, `brand_voice`, `signature_line`) — coordinated contract changes: command-center folds them into schema §3 + §6, ghl-command-mcp adds them to the installer. Every earlier key and label is unchanged, so a form installed from v1.1 still maps; only the new answers are missing from it.
8
+
9
+ **Form display order** (sections are contiguous so each gets one header): Contact → A Business basics → **G Your team** → B Offer → C Audience → D Goal → **H Calendars and phone** → E Channels → F Assets → **I Your voice**.
8
10
 
9
11
  Each question maps 1:1 to a Brief field (schema §4) via its `key`. The mapping column is authoritative for the normalizer.
10
12
 
@@ -33,6 +35,14 @@ Each question maps 1:1 to a Brief field (schema §4) via its `key`. The mapping
33
35
  | A7 | Roughly how many new leads per month? | `monthly_lead_volume` | dropdown | no | Options: `Under 100`, `100-500`, `500-1000`, `1000+`. Sizes SMS phone numbers (~1 per 500/mo) and send volume. Leave blank if unsure. |
34
36
  | A8 | Your business hours | `business_hours` | textarea | no | e.g. "Mon-Fri 9-6, Sat 10-2". Sets your calendar's default booking hours. Leave blank for Mon-Fri 9-5. |
35
37
 
38
+ ## Section G — Your team *(v1.2)*
39
+
40
+ | # | Label (client sees) | `key` | Type | Required | Options / help |
41
+ |---|---|---|---|---|---|
42
+ | G1 | Staff members to set up (one per line: Name, email, role, mobile) | `staff_members` | textarea | no | Placeholder shows the exact line format: `Jane Smith, jane@yourclinic.com, Front desk, 555-123-4567`. Every person becomes a plan `users[]` entry; every notification / task / calendar points at one. Empty → the build still ships, with those steps marked *waiting for a staff member*. |
43
+ | G2 | Who should be notified about new leads? | `notify_name` | text | no | A name from the list above, or "the owner". |
44
+ | G3 | Who takes booking calls / follow-up calls? | `calls_name` | text | no | A name from the list above, or "the owner". |
45
+
36
46
  ## Section B — Offer and pricing
37
47
 
38
48
  | # | Label | `key` | Type | Required | Options / help |
@@ -59,6 +69,13 @@ Each question maps 1:1 to a Brief field (schema §4) via its `key`. The mapping
59
69
  | D3 | Do customers book appointments with you? | `booking_needed` | radio (yes/no) | yes | Yes if you take consults/appointments. *(Drives whether a calendar is built.)* |
60
70
  | D4 | Follow-up style | `follow_up_style` | dropdown | no | Options: `High-touch / multi-step`, `Light`, `Single confirmation`. How aggressively to follow up. |
61
71
 
72
+ ## Section H — Calendars and phone *(v1.2)*
73
+
74
+ | # | Label (client sees) | `key` | Type | Required | Options / help |
75
+ |---|---|---|---|---|---|
76
+ | H1 | How many booking calendars do you need, and what is each one for? | `booking_calendars` | textarea | no | Placeholder shows the format: `Calendar name, type: one-on-one / round-robin / class, who is on it, how long` — e.g. `New Patient Consult, round-robin, Jane Smith + Dr. Mark Lee, 45 minutes`. One line per calendar. *(Drives how many calendars are built, who is on each, and the slot length — "15 minutes" becomes `slotDuration: 15`; without it GoHighLevel builds 30-minute slots.)* |
77
+ | H2 | Do you already have a phone number in GoHighLevel? | `has_phone_number` | dropdown | no | Options: `Yes`, `No`, `Not sure`. (No "email" in any dropdown label — browser autofill, see E1.) *(With SMS wanted, No / Not sure flags `phone_number_needed`.)* |
78
+
62
79
  ## Section E — Channels and tech
63
80
 
64
81
  | # | Label | `key` | Type | Required | Options / help |
@@ -79,6 +96,13 @@ Each question maps 1:1 to a Brief field (schema §4) via its `key`. The mapping
79
96
  | F3 | Brand assets | `brand_assets` | text | no | Logo, colors, domain available — whatever you have. |
80
97
  | F4 | Anything else we should know? | `anything_else` | textarea | no | Constraints, compliance limits, preferences, context. |
81
98
 
99
+ ## Section I — Your voice *(v1.2)*
100
+
101
+ | # | Label (client sees) | `key` | Type | Required | Options / help |
102
+ |---|---|---|---|---|---|
103
+ | I1 | Brand voice in three words | `brand_voice` | text | no | e.g. "warm, direct, unhurried". The copywriter's tone brief. |
104
+ | I2 | A line you always say to new clients (your voice) | `signature_line` | textarea | no | Word for word; it is reused in the welcome email / first text so the messages sound like the client. |
105
+
82
106
  ---
83
107
 
84
108
  ## Key → Brief field map (authoritative for the normalizer)
@@ -114,12 +138,28 @@ Each question maps 1:1 to a Brief field (schema §4) via its `key`. The mapping
114
138
  | `existing_workflows` | `assets.existingWorkflows` |
115
139
  | `brand_assets` | `assets.brand` |
116
140
  | `anything_else` | `assets.notes` |
141
+ | `staff_members` | `team.staff` (parse lines → `[{name, email, role?, mobile?}]`; an unreadable line → `warnings[]`) |
142
+ | `notify_name` | `team.notifyName` |
143
+ | `calls_name` | `team.callsName` |
144
+ | `booking_calendars` | `calendars` (parse lines → `[{name, type, staffNames}]`, type ∈ `one_on_one` / `round_robin` / `class`) |
145
+ | `has_phone_number` | `channels.hasPhoneNumber` (`yes` / `no` / `unsure`) |
146
+ | `brand_voice` | `voice.threeWords` |
147
+ | `signature_line` | `voice.signatureLine` |
117
148
 
118
149
  ### Derived `flags` (normalizer computes, not asked)
119
150
  - `needs_a2p` ← `sms_desired == yes` AND `a2p_status != approved`
120
151
  - `stripe_not_connected` ← `payment_processor == "Stripe not connected"`
121
152
  - `calendar_oauth_needed` ← `booking_needed == yes` AND `calendar_connected == no`
122
153
  - `email_domain_needed` ← `email_ready == no`
154
+ - `phone_number_needed` ← `sms_desired == yes` AND `has_phone_number` answered anything but `Yes` *(v1.2)*
155
+
156
+ ### `validate_brief` warnings (v1.2 — the gaps a schema-valid brief can still carry)
157
+ Returned as `warnings[]` next to `errors[]`; never fatal. The plan-gen skill reads them and either asks or marks the step as waiting.
158
+ - **No staff listed** → "notifications will be marked as waiting for a staff member" (`user.__pending__` in the plan).
159
+ - **A calendar named without staff** → it cannot take bookings until someone is put on it.
160
+ - **A calendar lists a name not in the staff list** / **notify or calls name not in the staff list** ("the owner" is always accepted).
161
+ - **Booking wanted but no calendar described** → the plan would have to assume one.
162
+ - Plus every parse-time note the normalizer left in `brief.warnings` (e.g. `Staff line 2 ("Bob") was skipped: no email address found`).
123
163
 
124
164
  ---
125
165
 
@@ -139,3 +179,24 @@ Fold-in notes:
139
179
  - **`monthly_lead_volume` refined from the original "(text)" proposal → dropdown buckets (`SINGLE_OPTIONS`).** Free text ("a few hundred?") is unparseable for the 1-number-per-500 math; fixed buckets map cleanly to a phone-number count. Flagging the type change explicitly so the installer builds a dropdown, not a text field.
140
180
  - All three land under the Brief's `business.*` namespace (new sub-keys `teamSize`, `monthlyLeadVolume`, `hours`) — purely additive, no existing field changes, so `schemaVersion` stays `0.1`.
141
181
  - No new derived `flags`. `monthly_lead_volume` is read directly by the clinic_launch_a2p preset at phone-provisioning time; absent → the preset keeps its current ask-at-handoff default. `business_hours` absent → skill keeps the Mon-Fri 9-5 default + operator-edit flag. `team_size` absent → skill keeps "automate first touch, human follow-up light."
182
+
183
+ ---
184
+
185
+ ## Tier 1 v2 additions — folded in 2026-08-26 (question-set 0.2)
186
+
187
+ The owner inspected a real build and found it had created **one** user and **one** calendar — because the intake never asked. His words: *"Staff: if we are going to create one, why wouldn't we ask for the info on all staff members who should be included?"* and *"Calendar: are we sure we only need one calendar? Did we even ask?"* Seven keys, all optional (the required floor is unchanged):
188
+
189
+ | `key` | Form type | GHL `dataType` | Brief path (§4) | Brief type | Drives in plan |
190
+ |---|---|---|---|---|---|
191
+ | `staff_members` | textarea | `LARGE_TEXT` | `team.staff` | `[{name, email, role?, mobile?}]` | `users[]` (one per line) + every `userRef` on notifications, tasks, assignments, calendars |
192
+ | `notify_name` | text | `TEXT` | `team.notifyName` | string | which `user.*` the new-lead `internal_notification` points at (or `user.__pending__`) |
193
+ | `calls_name` | text | `TEXT` | `team.callsName` | string | task assignee / `assign_user` on the speed-to-lead |
194
+ | `booking_calendars` | textarea | `LARGE_TEXT` | `calendars` | `[{name, type, staffNames}]` | how many `calendars[]` are built, their `calendarType` and `teamMemberRefs` |
195
+ | `has_phone_number` | dropdown | `SINGLE_OPTIONS` | `channels.hasPhoneNumber` | `yes` / `no` / `unsure` | the phone-number handoff (with SMS wanted) |
196
+ | `brand_voice` | text | `TEXT` | `voice.threeWords` | string | tone of every email / SMS template |
197
+ | `signature_line` | textarea | `LARGE_TEXT` | `voice.signatureLine` | string | reused verbatim in the welcome email / first text |
198
+
199
+ Parse rules (normalizer, pure, never throws):
200
+ - **Staff lines**: fields split on `,` `|` `;` or tab; order is not trusted — the email is whichever field looks like one, the mobile whichever looks like a phone number, the name the first remaining field, the role the rest. No name or no email → the line is **skipped and reported** in `warnings[]`, never silently dropped.
201
+ - **Calendar lines**: first field is the name; the type is whichever field reads like `one-on-one` / `1:1`, `round-robin`, or `class` / `group` (a `type:` prefix is fine); everything else is people, split on `+`, `&`, `and`, `/`. No recognizable type → assumed one-on-one **and reported**.
202
+ - The derived fieldKeys for these seven are produced by the same rule as the rest (`contact.intake_<slug>`) and are **derived, not yet live-captured**; the installer's verify-after step confirms the real key on first install.