@elitedcs/ghl-mcp 3.88.3 → 3.89.0-beta.10

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.
@@ -51,7 +51,7 @@ The Build Plan is the reviewable, editable output of the skill. **Refs only, no
51
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).
52
52
 
53
53
  ## Logical workflow actions (executor expands to native)
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.
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}`, `enable_dnd {}` *(turns on Do Not Disturb for outbound messages, e.g. after a STOP reply; emitted as GHL's `dnd_contact` step)*, `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
55
 
56
56
  ## v2 rules the validator enforces (2026-08-26) — read before writing workflows
57
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.
@@ -56,6 +56,8 @@ Library (v2.0.0): `med-spa` (default), `generic-client` (agency / other fallback
56
56
 
57
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.
58
58
 
59
+ `conventions` values are strings the model reads, with one switch the composer itself reads: `"quotesNeverAutomatic": true` means a client quote never goes into a message automatically, even with written consent. The claims guard then treats a consented testimonial as not allowed and looks for the words of every intake quote, so a copy override that quotes one (even a size result with "results vary") falls back to the preset's own wording. The wellness clinic preset sets it.
60
+
59
61
  ## The `skeleton` object
60
62
 
61
63
  `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:
@@ -96,7 +98,7 @@ For list-valued structure the brief can override:
96
98
  "users": { "fillFrom": "team.staff", "transform": "userList", "default": [] }
97
99
  ```
98
100
 
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.
101
+ - `stageList`: `["new lead","contacted",...]` → `[{ref: "stage.<slug>", name, position}]`. Workflows that point at `stage.*` refs resolve against whichever set won. The client's list is the whole pipeline: a referenced default stage the client did not name is mapped onto one of theirs through the default stage's `aliases` array (whole-word match on the slug; an alias written `"=consultation"` matches only a stage named exactly that; a client stage another default stage's alias names more specifically is left to that stage; each client stage goes to at most one default stage, the one whose alias matches it most specifically, and the loser tries its next alias; a stage still working the lead is never mapped onto a terminal one such as lost, no show, no sale, closed, won, not a fit, disqualified, dead, cancelled, declined, DND, churned, lapsed or archived; the first default stage falls back to the client's first stage), and the mapping is noted. A workflow may carry `stagePreference: { "stage.contacted": ["no show", ...] }` to prefer a client stage for one ref in that workflow only (No-Show Rescue → the client's "No show" stage); it is stripped from the plan. Only when nothing maps is the default stage kept, placed in preset order right after the client stage the previous preset stage landed on (else before the one the next preset stage landed on, else before the first terminal stage), never after Lost. Never ship a dead ref.
100
102
  - `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.
101
103
 
102
104
  ### 4. `templates` and `templateRef` (v2) — every message lives once, complete
@@ -158,6 +160,8 @@ Presets must use GHL-correct enum values so the emitted plan passes ghl-command-
158
160
  - 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.
159
161
  - `handoff.a2p.blocks` MUST list every SMS-bearing **workflow** ref (the validator checks it) — only an explicit `workflow.*` ref keeps a workflow DRAFT.
160
162
  - Max 40 actions per workflow (a `find_opportunity` counts head + 2 transitions + children); `find_opportunity` must be the last action.
163
+ - **Texting rules (2026-09-29, proven on live clinic accounts; enforced for every preset by `src/intake-to-build/preset-message-rules.test.ts`).** SMS bodies use plain GSM-7 characters only: no long or short dashes, curly quotes, one-character ellipsis or emoji (one long dash took a live account from 22 billed parts per contact to 47). The first text of every nurture, win-back or reactivation workflow ends with `Reply STOP to opt out.` (or `Reply N to opt out.` where a reply of N is handled) on a line of its own. A keyword trigger carries `"keyword"` plus `"keywordMatch": "exact"`; never `"contains"` (a contains-STOP opted out a patient who asked "when should I stop eating").
164
+ - **No claims in preset copy.** No message may make an outcome or health claim (pain, sleep, inflammation relief, cures, weight or inch loss, guaranteed results, numeric results) or quote a named result ("Sarah lost 3 inches"). The composer fails a preset whose own words do (`src/intake-to-build/claims-guard.ts` holds the word list). Client proof comes only from the brief, with consent: `proof.testimonials[]` with `consent: true`, or `offer.claims[]` that say "results vary". Consent covers using the quote and name, not a claim inside it: a size or weight result goes out only with "results vary" in the same message, and a health claim never does.
161
165
 
162
166
  > 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
167