@elitedcs/ghl-mcp 3.71.0 → 3.72.1
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.
- package/CHANGELOG.md +70 -2
- package/README.md +4 -4
- package/dist/capture-helper.js +10 -1
- package/dist/index.js +8052 -2156
- package/guide/guide.html +2 -1
- package/package.json +5 -8
- package/skills/blueprint/SKILL.md +2 -0
- package/skills/blueprint/examples/medspa-approval-view.md +58 -53
- package/skills/blueprint/examples/medspa-brief.json +70 -8
- package/skills/blueprint/examples/medspa-build-plan.json +1435 -123
- package/skills/blueprint/examples/medspa-dry-run-report.md +3 -1
- package/skills/blueprint/examples/sample-approval-view.md +20 -61
- package/skills/blueprint/examples/sample-brief.json +95 -7
- package/skills/blueprint/examples/sample-build-plan.json +1448 -119
- package/skills/blueprint/examples/validate-plan.cjs +195 -13
- package/skills/blueprint/presets/clinic.md +60 -0
- package/skills/blueprint/presets/clinic.preset.json +1737 -0
- package/skills/blueprint/presets/coach.md +58 -0
- package/skills/blueprint/presets/coach.preset.json +1723 -0
- package/skills/blueprint/presets/ecommerce.md +54 -0
- package/skills/blueprint/presets/ecommerce.preset.json +1287 -0
- package/skills/blueprint/presets/generic-client.md +49 -27
- package/skills/blueprint/presets/generic-client.preset.json +1552 -122
- package/skills/blueprint/presets/local-service.md +58 -0
- package/skills/blueprint/presets/local-service.preset.json +1733 -0
- package/skills/blueprint/presets/med-spa.md +47 -48
- package/skills/blueprint/presets/med-spa.preset.json +1557 -111
- package/skills/blueprint/references/agency-os-detection.md +1 -1
- package/skills/blueprint/references/brief-schema.md +48 -1
- package/skills/blueprint/references/build-plan-schema.md +66 -5
- package/skills/blueprint/references/copy-guide.md +167 -0
- package/skills/blueprint/references/intake-question-set.md +64 -3
- package/skills/blueprint/references/preset-format.md +97 -51
- package/templates/action-schemas.json +12 -0
- package/templates/clinic-medspa.json +1 -1
- package/skills/blueprint/presets/clinic-launch-a2p.md +0 -39
- package/skills/blueprint/presets/clinic-launch-a2p.preset.json +0 -337
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Preset Format & Versioning
|
|
2
2
|
|
|
3
|
-
STATUS:
|
|
3
|
+
STATUS: v2, 2026-08-26 (v1 2026-06-15, owner atlas). Defines how Blueprint presets are authored, versioned, and consumed by the plan-gen skill. Ratifies schema §7 item 4 (preset → plan determinism) and adds the Tier 1 v2 layer: users, templates, `userRef`, the tag hand-off, and the depth rules every preset must meet.
|
|
4
4
|
|
|
5
5
|
## What a preset is
|
|
6
6
|
|
|
@@ -8,48 +8,68 @@ A preset is a **build-plan template**: it is authored in the exact vocabulary of
|
|
|
8
8
|
|
|
9
9
|
This keeps preset → plan **deterministic** and keeps the preset in the same shape the operator reviews and the phase-2 executor consumes. There is no separate "preset language" to learn beyond §5 + the two additions below.
|
|
10
10
|
|
|
11
|
-
> Ratification of §7 #4: **the preset fixes the skeleton** (pipeline stage *set*, the workflow *set*, the asset *list*, the handoff *set*,
|
|
11
|
+
> Ratification of §7 #4: **the preset fixes the skeleton** (pipeline stage *set*, the workflow *set*, the asset *list*, the handoff *set*, the complete copy). **The brief fills** names, people, copy specifics, toggles, and any list the brief overrides (stages from `goal.salesStages`, users from `team.staff`, calendars from `calendars[]`). **Brief `flags` add or drop conditional items.** The skill does not invent structure the preset does not declare; it only fills, drops, expands, and rewrites copy. Same brief + same preset version → same plan skeleton, every time.
|
|
12
|
+
|
|
13
|
+
## What v2 depth means (owner review, 2026-08-26)
|
|
14
|
+
|
|
15
|
+
"Every single WF is so short, they are not doing much for a business." Every preset in the library now ships:
|
|
16
|
+
|
|
17
|
+
| Workflow | Minimum | What the library ships |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Speed to Lead | 10+ actions: instant text + email, call task to a real user, staff alert, opportunity card, 2nd and 3rd touch, and a hand-off into the nurture as the **last** action | 14 actions, 5 touches in 24 h, `assign_user`, then `add_contact_tag nurture-start` |
|
|
20
|
+
| Missed Call Text-Back | `call_status` trigger scoped to no-answer / busy / voicemail | 7 actions: text-back, tag, alert, call-back task, +1 h text |
|
|
21
|
+
| Lead Nurture | **never under 30 days of waits**, 8–12 touches, `stopOnResponse` | 28 actions, 11 touches, 31 days |
|
|
22
|
+
| Replied – Stop & Route | remove from every sequence → note → tag → notify | 9 actions incl. a 15-minute reply task |
|
|
23
|
+
| Win-back | 30+ days, 4–6 touches | 13 actions, 5 touches, 31 days |
|
|
24
|
+
| Appointment (when the preset has a calendar) | confirm & move, reminders, no-show rescue | 8 + 5 + 14 actions |
|
|
25
|
+
|
|
26
|
+
Staff notifications and tasks are **always included**, even when the account has no staff yet: the step stays and is reported as waiting (`user.__pending__`). Copy is complete and written to `references/copy-guide.md`. `examples/validate-plan.cjs` enforces the rules on presets and plans alike.
|
|
12
27
|
|
|
13
28
|
## File layout
|
|
14
29
|
|
|
15
|
-
Each preset is two files in `
|
|
30
|
+
Each preset is two files in `skills/blueprint/presets/`:
|
|
16
31
|
|
|
17
32
|
- `<preset-id>.preset.json` — the machine-readable template the skill loads.
|
|
18
|
-
- `<preset-id>.md` — a human companion: what this preset builds, when it is selected,
|
|
33
|
+
- `<preset-id>.md` — a human companion: what this preset builds, when it is selected, the workflow table, and the copy notes. (Reviewers read this; the skill reads the JSON.)
|
|
34
|
+
|
|
35
|
+
Library (v2.0.0): `med-spa` (default), `generic-client` (agency / other fallback), `clinic`, `local-service`, `coach`, `ecommerce`, plus `clinic-launch-a2p` (the launch-event preset, v1 shape, selected explicitly). The industry slugs mirror `src/intake-to-build/customization.ts` and `src/plan-guide.ts` maps each slug to its preset.
|
|
19
36
|
|
|
20
37
|
## Preset JSON envelope
|
|
21
38
|
|
|
22
39
|
```jsonc
|
|
23
40
|
{
|
|
24
41
|
"presetId": "med_spa",
|
|
25
|
-
"presetVersion": "
|
|
42
|
+
"presetVersion": "2.0.0", // semver; bump on any skeleton change
|
|
26
43
|
"schemaVersion": "0.1", // the build-plan schema this targets
|
|
27
44
|
"title": "Med Spa",
|
|
28
|
-
"description": "One-
|
|
45
|
+
"description": "One-paragraph summary: what it builds, how deep.",
|
|
29
46
|
"default": true, // exactly one preset across the library is the default
|
|
30
47
|
"selectors": { // how the skill picks this preset
|
|
31
48
|
"businessTypes": ["Med spa","Medical spa","Medical aesthetics","Aesthetics"],
|
|
32
49
|
"aliases": ["med_spa","medspa","medical_spa","aesthetics","injectables"]
|
|
33
50
|
},
|
|
34
|
-
"source": "
|
|
51
|
+
"source": "where the skeleton was harvested from",
|
|
52
|
+
"conventions": { /* the v2 rules restated for the model reading this preset */ },
|
|
35
53
|
"skeleton": { /* §5 build-plan shape with tokens + conditionalOn, see below */ }
|
|
36
54
|
}
|
|
37
55
|
```
|
|
38
56
|
|
|
39
|
-
`selectors.businessTypes` are the `business_type` answers that route to this preset. `selectors.aliases` are the `brief.preset` enum values that resolve to this preset (e.g. `preset: "med_spa"` resolves to the med-spa preset, which claims that alias). Exactly one preset across the library has `"default": true` —
|
|
57
|
+
`selectors.businessTypes` are the `business_type` answers that route to this preset. `selectors.aliases` are the `brief.preset` enum values that resolve to this preset (e.g. `preset: "med_spa"` resolves to the med-spa preset, which claims that alias). Exactly one preset across the library has `"default": true` — **`med_spa`**. `generic` is `default:false`, the neutral fallback for agency / other / unmatched types.
|
|
40
58
|
|
|
41
59
|
## The `skeleton` object
|
|
42
60
|
|
|
43
|
-
`skeleton` mirrors the §5 build-plan top level — `pipelines`, `customFields`, `tags`, `customValues`, `calendars`, `forms`, `funnels`, `emails`, `sms`, `workflows`, `handoffs`. Every item is a §5 object, optionally annotated with:
|
|
61
|
+
`skeleton` mirrors the §5 build-plan top level — `users`, `pipelines`, `customFields`, `tags`, `customValues`, `calendars`, `forms`, `funnels`, `templates`, `emails`, `sms`, `workflows`, `handoffs`. Keys starting with `_` are author notes and are dropped at fill time. Every item is a §5 object, optionally annotated with:
|
|
44
62
|
|
|
45
63
|
### 1. Template tokens — `{{ ... }}`
|
|
46
|
-
A `{{path}}` inside any string field is replaced with the brief value at that path. Dotted paths read the brief (`{{business.name}}`, `{{offer.summary}}`). Supports a fallback with `||`:
|
|
64
|
+
A `{{path}}` inside any string field is replaced with the brief value at that path. Dotted paths read the brief (`{{business.name}}`, `{{offer.summary}}`); `[n]` indexes an array (`{{audience.painPoints[0]}}`). Supports a fallback with `||`:
|
|
47
65
|
|
|
48
66
|
```jsonc
|
|
49
67
|
{ "ref": "pipeline.main", "name": "{{business.name}} Pipeline || Patient Journey" }
|
|
50
68
|
```
|
|
51
69
|
|
|
52
|
-
If `business.name` is present, it fills; otherwise the literal after `||` is used. A token that resolves to empty with no fallback → the skill omits the field (never emits an empty string) and notes the default it used.
|
|
70
|
+
If `business.name` is present, it fills; otherwise the literal after `||` is used. A token that resolves to empty with no fallback → the skill omits the field (never emits an empty string) and notes the default it used. Inside copy, a token whose value starts a Capitalized word mid-sentence is lower-cased on fill (`{{offer.leadMagnet}}` = "Free skin assessment" → "your free skin assessment request").
|
|
71
|
+
|
|
72
|
+
**GHL merge fields pass through untouched.** A `{{...}}` whose first segment is `contact`, `custom_values`, `appointment`, `user`, `location` or `message` is a GoHighLevel merge field, not a preset token; the fill step leaves it exactly as written. The copy guide (§7) lists the allowed set.
|
|
53
73
|
|
|
54
74
|
### 2. `conditionalOn` — include/drop the whole item
|
|
55
75
|
A string boolean expression over the brief. If it evaluates false, the skill **drops the item entirely** (and anything that referenced it — the skill prunes dangling refs and records the prune in the plan summary). Grammar is intentionally tiny:
|
|
@@ -58,73 +78,99 @@ A string boolean expression over the brief. If it evaluates false, the skill **d
|
|
|
58
78
|
- `channels.sms == true`
|
|
59
79
|
- `flags includes needs_a2p`
|
|
60
80
|
- `channels.payment != "Stripe connected"`
|
|
81
|
+
- `staff is empty` (v2: `brief.team.staff` absent or empty)
|
|
61
82
|
- combine with `&&` / `||`
|
|
62
83
|
|
|
84
|
+
`conditionalOn` may sit on any item, including a single workflow action (`send_sms` steps carry `channels.sms == true`) and a single pipeline stage.
|
|
85
|
+
|
|
63
86
|
```jsonc
|
|
64
87
|
{ "ref": "calendar.consult", "conditionalOn": "goal.bookingNeeded == true", "name": "Consultation", ... }
|
|
65
88
|
{ "ref": "handoff.a2p", "conditionalOn": "flags includes needs_a2p", ... }
|
|
66
89
|
```
|
|
67
90
|
|
|
68
91
|
### 3. `fillFrom` — populate a list from the brief, else the preset default
|
|
69
|
-
For list-valued structure the brief can override
|
|
92
|
+
For list-valued structure the brief can override:
|
|
70
93
|
|
|
71
94
|
```jsonc
|
|
72
|
-
{
|
|
73
|
-
"
|
|
74
|
-
"name": "{{business.name}} Pipeline || Sales Pipeline",
|
|
75
|
-
"stages": {
|
|
76
|
-
"fillFrom": "goal.salesStages",
|
|
77
|
-
"transform": "stageList", // skill turns ["new lead","contacted",...] into [{ref,name,position}]
|
|
78
|
-
"default": [
|
|
79
|
-
{ "ref": "stage.new_lead", "name": "New Lead", "position": 0 },
|
|
80
|
-
{ "ref": "stage.contacted", "name": "Contacted", "position": 1 }
|
|
81
|
-
/* ... preset's own default stage set ... */
|
|
82
|
-
]
|
|
83
|
-
}
|
|
84
|
-
}
|
|
95
|
+
"stages": { "fillFrom": "goal.salesStages", "transform": "stageList", "default": [ { "ref": "stage.new_lead", "name": "New Lead" } /* ... */ ] }
|
|
96
|
+
"users": { "fillFrom": "team.staff", "transform": "userList", "default": [] }
|
|
85
97
|
```
|
|
86
98
|
|
|
87
|
-
|
|
99
|
+
- `stageList`: `["new lead","contacted",...]` → `[{ref: "stage.<slug>", name, position}]`. Workflows that point at `stage.*` refs resolve against whichever set won; a referenced stage not in the chosen set is mapped to the nearest equivalent (new-lead → the first stage; booked → any stage whose name contains "book" / "consult" / "visit" / "estimate" / "call") and the mapping is noted. Never ship a dead ref.
|
|
100
|
+
- `userList` (v2): each `brief.team.staff[]` entry → `{ref: "user.<slug of name>", firstName, lastName, email, role, phone?}`. `role` is `admin` for the first person / anyone whose title says owner, admin or director; `user` otherwise. Emails must be unique.
|
|
88
101
|
|
|
89
|
-
### 4. `
|
|
90
|
-
|
|
102
|
+
### 4. `templates` and `templateRef` (v2) — every message lives once, complete
|
|
103
|
+
`skeleton.templates.emails[]` = `{ref: "email_template.<slug>", name, subject, html}` and `skeleton.templates.sms[]` = `{ref: "sms_template.<slug>", name, body}`. **The copy is complete and send-ready** in the preset, written from placeholder brief fields to `references/copy-guide.md`. The matching `emails[]` / `sms[]` assets carry `templateRef` plus a `copyDirection` (the job of that message in the sequence) and `mergeTags`; send steps reference the asset (`emailRef` / `smsRef`) or the template (`templateRef`) — one of the two is required.
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
At fill time the skill **rewrites every template from the real brief** (offer, pain points, objections, prices, lead magnet, `voice.threeWords` for tone, `voice.signatureLine` verbatim in the instant email and text) keeping the cadence, the single CTA and the merge fields, then writes the result to **both** the template and the asset `body`. The plan never carries an outline where a body belongs (`needsContent` will not build).
|
|
106
|
+
|
|
107
|
+
### 5. `userRef` and the role placeholder (v2)
|
|
108
|
+
Every `internal_notification`, `task_notification` and `assign_user` carries `userRef`; every calendar carries `teamMemberRefs`. In a preset the ref is the **role placeholder `user.owner`**, never a real user. The fill step resolves it per step:
|
|
109
|
+
|
|
110
|
+
| Where | Resolves to |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `internal_notification.userRef` | the user named by `brief.team.notifyName` |
|
|
113
|
+
| `task_notification.userRef`, `assign_user.userRef` | the user named by `brief.team.callsName` |
|
|
114
|
+
| `calendars[].teamMemberRefs` | that calendar's `staffNames` from `brief.calendars[]` |
|
|
115
|
+
| "the owner" / "me" / a name not in the staff list | the first admin in `users[]` |
|
|
116
|
+
| no staff in the brief at all | the sentinel `user.__pending__` on every such step; the step is **kept**, the build reports it as waiting for a staff member, and `handoff.add_staff` holds that workflow DRAFT |
|
|
117
|
+
|
|
118
|
+
Never write a literal GHL user id in a preset (the legacy `to` / `assignedTo` slots).
|
|
119
|
+
|
|
120
|
+
### 6. Calendars from the brief (v2)
|
|
121
|
+
The preset declares one booking calendar (`teamMemberRefs: ["user.owner"]`). The first entry of `brief.calendars[]` maps onto it (name, type `one_on_one` → `event` / `round_robin` → `round_robin` / `class` → `class_booking`, staff, **length**); every further entry is appended as `calendar.<slug>` with its own `teamMemberRefs`. The appointment workflows attach to the first calendar. No calendars in the brief with `goal.bookingNeeded` true → keep the default and note the assumption.
|
|
122
|
+
|
|
123
|
+
**Slot length (finding 25):** `brief.calendars[].durationMinutes` (or the words in the calendar answer, "15 minutes" / "1 hour") → `calendars[].slotDuration` in minutes with `slotDurationUnit: "mins"`. The preset's own `slotDuration` is the fallback ONLY when the brief gives no length; a calendar with no `slotDuration` is built with GoHighLevel's 30-minute default (Timed Build 04 asked for 15 and 45 and got 30 twice). Never invent a length the brief or the preset does not give.
|
|
124
|
+
|
|
125
|
+
### 7. Hand-offs between sequences — a tag, never `add_to_workflow`
|
|
126
|
+
Speed to Lead ends with `add_contact_tag nurture-start`; Lead Nurture is triggered by `contact_tag nurture-start` and ends with `add_contact_tag winback-start`; Win-back is triggered by it. **Why a tag and not `add_to_workflow`:** both halves of the tag hand-off are live-proven action shapes captured from working GHL UI-built workflows in `templates/action-schemas.json` (the add-tag node and the tag trigger). `add_to_workflow` has no captured native shape there and has not been proven at runtime; a preset may only use proven shapes. Each sequence removes its own trigger tag as its first action so a later hand-off can start it again (GHL fires a tag trigger only when the tag is added, not when it is already present).
|
|
127
|
+
|
|
128
|
+
The validator enforces: a `/speed/i` workflow must **end** with the hand-off (`E_NO_HANDOFF`); a `/nurture/i` workflow's waits must total ≥ 30 days (`E_NURTURE_TOO_SHORT`); a `/win.?back/i` workflow's waits must total ≥ 30 days (`E_WINBACK_TOO_SHORT`, this library's own rule).
|
|
129
|
+
|
|
130
|
+
### 8. `copyDirection` — what a message is for
|
|
131
|
+
On `emails[]` / `sms[]` / `funnels[].pages`, `copyDirection` is the instruction the rewrite honors (day 8 gives a tip, day 14 handles price, day 27 is the decision sheet). For pages it drives the page outline. It is not the copy; the template is.
|
|
98
132
|
|
|
99
133
|
## The skill's deterministic fill algorithm (consumes a preset)
|
|
100
134
|
|
|
101
135
|
1. Resolve preset (brief.preset alias → preset, else business_type → preset, else default).
|
|
102
|
-
2. Deep-copy `skeleton
|
|
103
|
-
3.
|
|
104
|
-
4.
|
|
105
|
-
5. Substitute every `{{token}}` (with `||` fallback); omit-and-note where empty.
|
|
106
|
-
6.
|
|
107
|
-
7.
|
|
108
|
-
8.
|
|
109
|
-
9.
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
2. Deep-copy `skeleton`; drop every `_`-prefixed key.
|
|
137
|
+
3. Resolve every `fillFrom` (stages from `goal.salesStages`, users from `team.staff`); apply `brief.calendars[]` (§6).
|
|
138
|
+
4. Evaluate every `conditionalOn`; drop false items; prune now-dangling refs (record prunes). Re-number stage positions.
|
|
139
|
+
5. Substitute every `{{token}}` (with `||` fallback); omit-and-note where empty; leave GHL merge fields untouched.
|
|
140
|
+
6. Resolve the `user.owner` placeholder per step (§5); set `cv.owner_first_name` from the notify user.
|
|
141
|
+
7. Derive flag-driven handoffs not already present (a2p, stripe, calendar_oauth, add_staff, email_domain, phone_number).
|
|
142
|
+
8. Rewrite every template from the brief per `references/copy-guide.md`; write the result to the template and the asset `body`.
|
|
143
|
+
9. Map any workflow `stage.*` ref not in the chosen stage set to its nearest equivalent (note it).
|
|
144
|
+
10. Set `buildOrder` (`user.*` first, `email_template.*` / `sms_template.*` before `workflow.*`), compute `summary`, leave `idMap` empty.
|
|
145
|
+
11. Run `examples/validate-plan.cjs` (and `validate_build_plan`), fix, then render the §5A approval view.
|
|
146
|
+
|
|
147
|
+
Steps 2–7 and 9–10 are pure and order-independent of the model's discretion — that is what makes the skeleton deterministic. Step 8 (copy) is the only generative step, and it is grounded in the brief, gated by the copy guide, and editable at review.
|
|
112
148
|
|
|
113
149
|
## Enum discipline (must pass the mcp validators)
|
|
114
150
|
|
|
115
151
|
Presets must use GHL-correct enum values so the emitted plan passes ghl-command-mcp's Zod / JSON-Schema validators (their §7 #1 ratification):
|
|
116
152
|
|
|
117
153
|
- `customFields[].dataType` ∈ `TEXT`, `LARGE_TEXT`, `NUMERICAL`, `PHONE`, **`MONETORY`** (GHL's own spelling — NOT "MONETARY"), `CHECKBOX`, `SINGLE_OPTIONS`, `MULTIPLE_OPTIONS`, `FLOAT`, `DATE`, `TEXTBOX_LIST`, `FILE_UPLOAD`, `SIGNATURE`. Choice types (`SINGLE_OPTIONS`/`MULTIPLE_OPTIONS`/`CHECKBOX`) MUST carry an `options` array.
|
|
118
|
-
- `calendars[].calendarType` ∈ `round_robin`, `event`, `class_booking`, `collective`, `service_booking`.
|
|
154
|
+
- `calendars[].calendarType` ∈ `round_robin`, `event`, `class_booking`, `collective`, `service_booking`. **A calendar that carries `teamMemberRefs` must NOT be `event`** *(finding 36)*: `event` calendars have no team-member concept — GoHighLevel answers 200 to the create and silently drops the people. Use `round_robin` even for a single person (a round-robin with one member behaves exactly like a one-on-one calendar and keeps them). `event` is only for a calendar nobody is assigned to.
|
|
119
155
|
- `customFields[].model` ∈ `contact`, `opportunity`.
|
|
120
|
-
-
|
|
156
|
+
- `users[].role` ∈ `admin`, `user`.
|
|
157
|
+
- **Trigger `type`** ∈ the executor's native set so triggers auto-build: `contact_tag`, `form_submission`, `appointment` (+ `appointmentStatus`), `customer_reply`, `pipeline_stage_updated`, `call_status` (+ `callStatuses`, `callDirection`), `inbound_webhook`, `payment_received`. `form_submitted`/`contact_replied` are accepted aliases; `tag_added`/`appointment_status`/`appointment_booked` are NOT recognized (they surface as a manual step) — never use them in a preset.
|
|
121
158
|
- handoff `owner` ∈ **`OPERATOR-UI`**, **`OPERATOR-EXT`**, **`TEAM`** (customer-facing, generic). The MCP still accepts + normalizes the legacy `JERRY-UI`/`JERRY-EXT`/`SASHA`, but presets EMIT the canonical labels.
|
|
122
|
-
- `
|
|
159
|
+
- `handoff.a2p.blocks` MUST list every SMS-bearing **workflow** ref (the validator checks it) — only an explicit `workflow.*` ref keeps a workflow DRAFT.
|
|
160
|
+
- Max 40 actions per workflow (a `find_opportunity` counts head + 2 transitions + children); `find_opportunity` must be the last action.
|
|
161
|
+
|
|
162
|
+
> The schema §5 contract and the live Zod schema (`src/intake-to-build/plan.ts`) are the sources of truth. If the doc and the Zod schema ever disagree, the Zod schema wins.
|
|
163
|
+
|
|
164
|
+
## Validating a preset
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
node skills/blueprint/examples/validate-plan.cjs skills/blueprint/presets/<id>.preset.json
|
|
168
|
+
```
|
|
123
169
|
|
|
124
|
-
|
|
170
|
+
A file with a top-level `skeleton` is validated as the **maximal plan**: every `conditionalOn` taken as true, every `fillFrom` resolved to its default, `user.owner` accepted as the role placeholder. The same script validates a filled plan (the worked examples) and applies the same v2 rules, plus copy hygiene (SMS length, subject length, template namespaces, custom values referenced by copy exist).
|
|
125
171
|
|
|
126
172
|
## Versioning & distribution
|
|
127
173
|
|
|
128
|
-
- **Version:** semver in `presetVersion`. Patch = copy/wording; minor = additive items; major = removed/renamed refs or changed skeleton shape. The generated plan records which preset+version produced it in its `summary` line so a build is reproducible.
|
|
129
|
-
- **Distribution:** presets ship **alongside the skill** (
|
|
130
|
-
- **Authoring a new preset:** copy
|
|
174
|
+
- **Version:** semver in `presetVersion`. Patch = copy/wording; minor = additive items; major = removed/renamed refs or changed skeleton shape. v2.0.0 (2026-08-26) is a major: the workflow set, the templates block and `userRef` are new, and `internal_notification.to` is gone. The generated plan records which preset+version produced it in its `summary` line so a build is reproducible.
|
|
175
|
+
- **Distribution:** presets ship **alongside the skill** (`skills/blueprint/presets/` travels with `SKILL.md`), and `src/plan-guide.ts` hands the matching preset, the worked example and the copy guide to the build stage's prompt. Adding a preset = drop two files in `presets/`, give it a `selector`, and map its industry slug in `plan-guide.ts`.
|
|
176
|
+
- **Authoring a new preset:** copy the closest `*.preset.json`, change `presetId`/`title`/`selectors`/`default:false`, edit the skeleton, rewrite every template for the vertical to the copy guide, write the `.md` companion, and run the validator. Keep the workflow set; the depth table above is the floor.
|
|
@@ -155,6 +155,18 @@
|
|
|
155
155
|
},
|
|
156
156
|
"notes": "dueDate is days from now as string. assignedTo is a user ID."
|
|
157
157
|
},
|
|
158
|
+
"assign_user": {
|
|
159
|
+
"example": {
|
|
160
|
+
"type": "assign_user",
|
|
161
|
+
"user_list": ["USER_ID"],
|
|
162
|
+
"only_unassigned_contact": false,
|
|
163
|
+
"traffic_split": "equally",
|
|
164
|
+
"traffic_weightage": { "USER_ID": 1 },
|
|
165
|
+
"traffic_index": [{ "id": "USER_ID", "indexes": [1] }],
|
|
166
|
+
"total_index": 1
|
|
167
|
+
},
|
|
168
|
+
"notes": "[UNVERIFIED — captured from GHL's builder bundle 2026-08-27 (client-app-automation-workflows.leadconnectorhq.com assets/index-7CgVurIr.js: the assign_user action class + its registry validator), not yet round-tripped live.] 'Assign to user' — the listed users become the contact owner. Linear actions-category node like add_contact_tag. user_list is REQUIRED and non-empty (GHL's builder validator: 'user_list_required'; every id is checked against the location's users — a dead id silently never assigns). Multiple users = round-robin; the equal-split bookkeeping the builder saves is weightage {id:1 each}, traffic_index [{id, indexes:[1..weight]}] with globally sequential indexes, total_index = sum of weights — single user: weightage {id:1}, index [{id, indexes:[1]}], total 1. only_unassigned_contact false always (re)assigns; true skips contacts that already have an owner. traffic_split 'unevenly' + custom weights and customUserList (custom-value mode) exist in the builder but are NOT built by us. LIVE TEST TO CLOSE: build one workflow with this node on a sandbox (MCP Testing), publish, run a test contact through, read back with get_workflow_full + get_contact — confirm the node persists byte-for-byte and the contact's assignedTo/owner becomes USER_ID; then remove this UNVERIFIED label."
|
|
169
|
+
},
|
|
158
170
|
"remove_from_workflow": {
|
|
159
171
|
"example": {
|
|
160
172
|
"workflowId": "TARGET_WORKFLOW_ID",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Preset: Clinic Launch Event + A2P (not default)
|
|
2
|
-
|
|
3
|
-
**File:** `clinic-launch-a2p.preset.json` · **id:** `clinic_launch_a2p` · **version:** 1.0.0 · **default:** no
|
|
4
|
-
**Source:** `client-provisioning-spec.md` + CLL `CLIENT_BUILD_AUTOMATION_BLUEPRINT.md` + `A2P_Client_Playbook.md` + QA v3 snapshot.
|
|
5
|
-
|
|
6
|
-
## When it is selected
|
|
7
|
-
Explicitly, or when the brief describes a **time-boxed on-site event + database-reactivation** model: event start/end dates, a deposit + balance, package tiers, and SMS reactivation of an existing opted-in list. It is **not** auto-selected by `business_type` — a med spa running a steady-state practice gets the `generic` preset, not this one.
|
|
8
|
-
|
|
9
|
-
## Why it exists
|
|
10
|
-
It proves the preset format scales to the hardest case: a build that is mostly **operator handoffs and compliance gates**, not auto-created objects. It also makes Jerry's most-proven asset (the CLL launch system) reusable as one preset without making the whole feature CLL-specific.
|
|
11
|
-
|
|
12
|
-
## What it builds (skeleton)
|
|
13
|
-
- **15-stage DBR pipeline** (DBR Lead → … → Package Sold → … → Lost) — the proven launch funnel.
|
|
14
|
-
- **9 representative intake/consent + sale fields** (the full launch intake is ~49 fields from the CLL onboarding/consent set; the skill creates the subset the workflows use). Package Recommended is genericized to Tier 1-4 with prices in custom values, not option labels.
|
|
15
|
-
- **17 tags** — DBR lines (1-4), DBR events, event status, routing.
|
|
16
|
-
- **25 custom values** — the launch data map (business/clinic identity, event window + capacity, deposit/trial/balance, package tier names + prices, and the link values filled by handoffs).
|
|
17
|
-
- **1 event calendar** — event-type, scoped to the event window, with a deposit.
|
|
18
|
-
- **1 patient intake & consent form** (phone not required — A2P opt-in rule).
|
|
19
|
-
- **No GHL funnel** — the opt-in presence is the dedicated **A2P compliance site** (6-page template), because A2P approval requires a real hosted site with exact consent HTML, not a GHL preview.
|
|
20
|
-
- **10 workflows** — DBR SMS line 1, auto-tag reply, send booking link (keyword BOOK), deposit-paid confirm, no-show, balance collection, package sold, post-event non-buyer (72hr), sold-out waitlist, STOP→DND. (The full system is ~27 WFs incl. lines 2-4; line 1 + the core set are encoded; additional lines clone the line-1 shape.)
|
|
21
|
-
|
|
22
|
-
> **Known manual step — SMS keyword triggers (decided 2026-06-27).** The keyword-routed workflows (BOOK / STOP / WAIT) use an `inbound_keyword` trigger that the executor does NOT auto-build yet. They build with all their steps, but the trigger is surfaced as a "set up this keyword trigger by hand in the GHL UI" step rather than auto-wiring. ghl-command-mcp decided to keep it manual for the v3.48.0 release (a new trigger type needs its own build-and-prove cycle) and queued a native build for when clinic keyword routing is prioritized. This is CLL-only and does not affect the med_spa/generic presets.
|
|
23
|
-
|
|
24
|
-
## The handoff chain (the heart of this preset)
|
|
25
|
-
This preset is handoff-dominated. In dependency order:
|
|
26
|
-
1. **add-staff** (blocks the calendar)
|
|
27
|
-
2. **stripe-products** → fills the deposit/balance/tier link custom values
|
|
28
|
-
3. **calendar-config** (dates, hours, deposit, connect) → fills the booking link
|
|
29
|
-
4. **phone-numbers** (1 per ~500, max 4) → blocks the DBR lines
|
|
30
|
-
5. **a2p-site** (deploy the compliance site, exact consent HTML) → blocks A2P submission
|
|
31
|
-
6. **a2p-brand-campaign** (submit, avoid forbidden words, 7-14 day review) → blocks all SMS sends
|
|
32
|
-
7. **conv-ai** (Chloe normal + sold-out prompts)
|
|
33
|
-
8. **sms-warming** (4-week ramp, parallel with A2P)
|
|
34
|
-
9. **list-import** (clean + split per line, test batch of 5) → blocks the DBR lines
|
|
35
|
-
|
|
36
|
-
The §5A approval view renders these as the "you must do manually, in this order" list, with each success check. That honesty — "here is everything GHL Command cannot do for you, sequenced" — is the point of the launch preset.
|
|
37
|
-
|
|
38
|
-
## A2P safety carried into the copy
|
|
39
|
-
SMS `copyDirection` enforces A2P-safe wording (clinic name not legal entity, opt-out lines, and explicit avoidance of the forbidden list: weight-loss / health-claim / income / 'operated by' / reactivation-lead-gen language). The compliance-site handoff carries the exact consent-HTML requirements that passed 8/8 in the playbook.
|