@elitedcs/ghl-mcp 3.89.0-beta.4 → 3.89.0-beta.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.89.0-beta.4",
3
+ "version": "3.89.0-beta.5",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 250 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",
@@ -175,15 +175,21 @@ const stageRefs = new Set();
175
175
  const days = (acts) => (acts || []).reduce((d, a) => a.type === 'wait' ? d + a.value * ({ minutes: 1 / 1440, hours: 1 / 24, days: 1 }[a.unit] || 0) : d, 0);
176
176
  const spanDays = (w) => { let d = days(w.actions); (w.actions || []).forEach(a => { if (a.type === 'find_opportunity') d += Math.max(days(a.found), days(a.notFound)); }); return d; };
177
177
  const nodeCount = (w) => (w.actions || []).reduce((n, a) => n + (a.type === 'find_opportunity' ? 3 + (a.found || []).length + (a.notFound || []).length : 1), 0);
178
- const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x.name || '') && x.trigger && x.trigger.tagRef).map(x => x.trigger.tagRef));
178
+ const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x.name || '') && x.trigger && !Array.isArray(x.trigger) && x.trigger.tagRef).map(x => x.trigger.tagRef));
179
179
  (plan.workflows || []).forEach(w => {
180
- if (!w.trigger || !w.trigger.type) E(`workflow ${w.ref}: missing trigger`);
181
- if (w.trigger) {
182
- need(w.trigger.formRef, `wf ${w.ref} trigger.formRef`); need(w.trigger.tagRef, `wf ${w.ref} trigger.tagRef`); need(w.trigger.calendarRef, `wf ${w.ref} trigger.calendarRef`);
183
- need(w.trigger.pipelineRef, `wf ${w.ref} trigger.pipelineRef`); need(w.trigger.hasTagRef, `wf ${w.ref} trigger.hasTagRef`);
184
- if (w.trigger.stageRef && !stageRefs.has(w.trigger.stageRef)) E(`wf ${w.ref} trigger.stageRef ${w.trigger.stageRef} not a defined stage`);
185
- if (w.trigger.type === 'call_status' && !(w.trigger.callStatuses && w.trigger.callStatuses.length)) E(`workflow ${w.ref}: a call_status trigger needs callStatuses (else it fires on EVERY call)`);
186
- if (['tag_added', 'appointment_status', 'appointment_booked'].includes(w.trigger.type)) E(`workflow ${w.ref}: trigger type "${w.trigger.type}" is not native — use contact_tag / appointment`);
180
+ // A preset may list trigger variants, each with its own conditionalOn (the
181
+ // composer keeps the first that holds, e.g. the intake's review timing): each
182
+ // variant is checked as a trigger; the last must be the unconditional default.
183
+ const variants = Array.isArray(w.trigger) ? w.trigger : (w.trigger ? [w.trigger] : []);
184
+ if (Array.isArray(w.trigger) && (!variants.length || variants[variants.length - 1].conditionalOn)) E(`workflow ${w.ref}: a trigger list must end with a variant that has no conditionalOn (the default)`);
185
+ if (!variants.length || variants.some(t => !t || !t.type)) E(`workflow ${w.ref}: missing trigger`);
186
+ for (const t of variants) {
187
+ if (!t) continue;
188
+ need(t.formRef, `wf ${w.ref} trigger.formRef`); need(t.tagRef, `wf ${w.ref} trigger.tagRef`); need(t.calendarRef, `wf ${w.ref} trigger.calendarRef`);
189
+ need(t.pipelineRef, `wf ${w.ref} trigger.pipelineRef`); need(t.hasTagRef, `wf ${w.ref} trigger.hasTagRef`);
190
+ if (t.stageRef && !stageRefs.has(t.stageRef)) E(`wf ${w.ref} trigger.stageRef ${t.stageRef} not a defined stage`);
191
+ if (t.type === 'call_status' && !(t.callStatuses && t.callStatuses.length)) E(`workflow ${w.ref}: a call_status trigger needs callStatuses (else it fires on EVERY call)`);
192
+ if (['tag_added', 'appointment_status', 'appointment_booked'].includes(t.type)) E(`workflow ${w.ref}: trigger type "${t.type}" is not native — use contact_tag / appointment`);
187
193
  }
188
194
  if (!Array.isArray(w.actions) || w.actions.length === 0) E(`workflow ${w.ref}: no actions`);
189
195
  if ((w.actions || []).length > 40) E(`workflow ${w.ref}: >40 actions (split required)`);
@@ -200,7 +206,7 @@ const nurtureTags = new Set((plan.workflows || []).filter(x => /nurture/i.test(x
200
206
  if (a.type === 'assign_user' && !a.userRef) E(`${at}: needs userRef`);
201
207
  if (a.stageRef && !stageRefs.has(a.stageRef)) E(`${at}: stageRef ${a.stageRef} not a defined stage`);
202
208
  if (a.type === 'wait' && !(a.value > 0 && ['minutes', 'hours', 'days'].includes(a.unit))) E(`${at}: wait needs value > 0 and unit minutes|hours|days`);
203
- if (a.type === 'wait_appointment' && (!w.trigger || w.trigger.type !== 'appointment')) W(`${at}: wait_appointment outside an appointment-triggered workflow`);
209
+ if (a.type === 'wait_appointment' && !variants.some(t => t && t.type === 'appointment')) W(`${at}: wait_appointment outside an appointment-triggered workflow`);
204
210
  };
205
211
  let seenFind = false;
206
212
  (w.actions || []).forEach((a, i) => {
@@ -58,18 +58,18 @@
58
58
  },
59
59
  {
60
60
  "ref": "stage.contacted",
61
- "aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up"],
61
+ "aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up", "working"],
62
62
  "name": "Contacted"
63
63
  },
64
64
  {
65
65
  "ref": "stage.visit_booked",
66
- "aliases": ["visit booked", "appointment booked", "consult booked", "consultation booked", "exam booked", "booked", "scheduled"],
66
+ "aliases": ["visit booked", "appointment booked", "consult booked", "consultation booked", "exam booked", "booked", "scheduled", "=consultation"],
67
67
  "name": "Visit Booked",
68
68
  "conditionalOn": "goal.bookingNeeded == true"
69
69
  },
70
70
  {
71
71
  "ref": "stage.showed",
72
- "aliases": ["showed", "attended", "visit completed", "exam completed"],
72
+ "aliases": ["showed", "attended", "visit completed", "exam completed", "consultation completed", "consultation done", "consultation attended", "consultation showed", "consultation complete", "consultation held"],
73
73
  "name": "Showed",
74
74
  "conditionalOn": "goal.bookingNeeded == true"
75
75
  },
@@ -274,6 +274,12 @@
274
274
  "name": "review-requested",
275
275
  "_note": "A review was asked for after a completed visit.",
276
276
  "conditionalOn": "goal.bookingNeeded == true"
277
+ },
278
+ {
279
+ "ref": "tag.service_done",
280
+ "name": "service-complete",
281
+ "_note": "Only when the intake chose to ask for a review when the work is done: the team adds it, Review Request starts from it and removes it.",
282
+ "conditionalOn": "goal.bookingNeeded == true && goal.reviewTiming == 'When you mark the work done'"
277
283
  }
278
284
  ],
279
285
  "_tagNote": "Interest tags mirror the What Brings You In options and are intentionally non-clinical. The skill may add service tags from offer.summary only if compliance_notes allow it.",
@@ -1072,6 +1078,7 @@
1072
1078
  "workflows": [
1073
1079
  {
1074
1080
  "ref": "workflow.speed_to_lead",
1081
+ "allowMultiple": false,
1075
1082
  "name": "Speed to Lead",
1076
1083
  "trigger": {
1077
1084
  "type": "form_submission",
@@ -1157,6 +1164,7 @@
1157
1164
  },
1158
1165
  {
1159
1166
  "ref": "workflow.missed_call",
1167
+ "allowMultiple": true,
1160
1168
  "name": "Missed Call Text-Back",
1161
1169
  "conditionalOn": "channels.sms == true",
1162
1170
  "trigger": {
@@ -1211,13 +1219,14 @@
1211
1219
  },
1212
1220
  {
1213
1221
  "ref": "workflow.lead_nurture",
1222
+ "allowMultiple": false,
1214
1223
  "name": "Lead Nurture (30 days)",
1215
1224
  "trigger": {
1216
1225
  "type": "contact_tag",
1217
1226
  "tagRef": "tag.nurture_start"
1218
1227
  },
1219
1228
  "stopOnResponse": true,
1220
- "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first so a later hand-off can start it again. Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1229
+ "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1221
1230
  "actions": [
1222
1231
  {
1223
1232
  "type": "remove_contact_tag",
@@ -1352,6 +1361,7 @@
1352
1361
  },
1353
1362
  {
1354
1363
  "ref": "workflow.reply_route",
1364
+ "allowMultiple": true,
1355
1365
  "name": "Replied - Stop & Route",
1356
1366
  "trigger": {
1357
1367
  "type": "customer_reply"
@@ -1406,6 +1416,7 @@
1406
1416
  },
1407
1417
  {
1408
1418
  "ref": "workflow.win_back",
1419
+ "allowMultiple": false,
1409
1420
  "name": "Win-back (30 days)",
1410
1421
  "trigger": {
1411
1422
  "type": "contact_tag",
@@ -1477,6 +1488,7 @@
1477
1488
  },
1478
1489
  {
1479
1490
  "ref": "workflow.appointment_booked",
1491
+ "allowMultiple": true,
1480
1492
  "name": "Visit Booked - Confirm & Move",
1481
1493
  "trigger": {
1482
1494
  "type": "appointment",
@@ -1498,6 +1510,15 @@
1498
1510
  "type": "remove_from_workflow",
1499
1511
  "workflowRef": "workflow.win_back"
1500
1512
  },
1513
+ {
1514
+ "type": "remove_from_workflow",
1515
+ "workflowRef": "workflow.no_show"
1516
+ },
1517
+ {
1518
+ "type": "remove_from_workflow",
1519
+ "workflowRef": "workflow.missed_call",
1520
+ "conditionalOn": "channels.sms == true"
1521
+ },
1501
1522
  {
1502
1523
  "type": "add_contact_tag",
1503
1524
  "tagRef": "tag.lc_booked"
@@ -1541,6 +1562,7 @@
1541
1562
  },
1542
1563
  {
1543
1564
  "ref": "workflow.appointment_reminders",
1565
+ "allowMultiple": true,
1544
1566
  "name": "Visit Reminders",
1545
1567
  "trigger": {
1546
1568
  "type": "appointment",
@@ -1579,6 +1601,7 @@
1579
1601
  },
1580
1602
  {
1581
1603
  "ref": "workflow.no_show",
1604
+ "allowMultiple": true,
1582
1605
  "stagePreference": { "stage.contacted": ["no show", "noshow", "missed appointment", "did not show"] },
1583
1606
  "name": "No-Show Rescue",
1584
1607
  "trigger": {
@@ -1668,6 +1691,7 @@
1668
1691
  },
1669
1692
  {
1670
1693
  "ref": "workflow.quote_follow_up",
1694
+ "allowMultiple": true,
1671
1695
  "name": "Quote Follow-up",
1672
1696
  "trigger": {
1673
1697
  "type": "contact_tag",
@@ -1729,24 +1753,44 @@
1729
1753
  },
1730
1754
  {
1731
1755
  "ref": "workflow.review_request",
1756
+ "allowMultiple": false,
1732
1757
  "name": "Review Request",
1733
- "trigger": {
1734
- "type": "appointment",
1735
- "appointmentStatus": "showed",
1736
- "calendarRef": "calendar.new_patient"
1737
- },
1758
+ "trigger": [
1759
+ {
1760
+ "conditionalOn": "goal.reviewTiming == 'When you mark the work done'",
1761
+ "type": "contact_tag",
1762
+ "tagRef": "tag.service_done"
1763
+ },
1764
+ {
1765
+ "type": "appointment",
1766
+ "appointmentStatus": "showed",
1767
+ "calendarRef": "calendar.new_patient"
1768
+ }
1769
+ ],
1738
1770
  "stopOnResponse": true,
1739
1771
  "conditionalOn": "goal.bookingNeeded == true",
1740
- "_design": "On a completed visit (status showed): tag, wait two hours, text the review link, then an email three days later if nothing came back. Any reply stops it. The review link is a custom value the operator pastes once (handoff.review_link). Closes the Layer 4 gap named on the hub: review requests had no build feature (2026-09-14).",
1772
+ "_design": "On a completed visit (status showed): tag, wait two hours, text the review link, then an email three days later if nothing came back. Any reply stops it. The review link is a custom value the operator pastes once (handoff.review_link). Closes the Layer 4 gap named on the hub: review requests had no build feature (2026-09-14). The intake's review timing question (goal.reviewTiming, 2026-09-29) can make it the day after the visit instead, or start it from the service-complete tag the team adds when the work is done.",
1741
1773
  "actions": [
1774
+ {
1775
+ "type": "remove_contact_tag",
1776
+ "tagRef": "tag.service_done",
1777
+ "conditionalOn": "goal.reviewTiming == 'When you mark the work done'"
1778
+ },
1742
1779
  {
1743
1780
  "type": "add_contact_tag",
1744
1781
  "tagRef": "tag.review_requested"
1745
1782
  },
1783
+ {
1784
+ "type": "wait",
1785
+ "value": 1,
1786
+ "unit": "days",
1787
+ "conditionalOn": "goal.reviewTiming == 'The day after a visit marked showed'"
1788
+ },
1746
1789
  {
1747
1790
  "type": "wait",
1748
1791
  "value": 2,
1749
- "unit": "hours"
1792
+ "unit": "hours",
1793
+ "conditionalOn": "goal.reviewTiming != 'The day after a visit marked showed'"
1750
1794
  },
1751
1795
  {
1752
1796
  "type": "send_sms",
@@ -1950,6 +1994,15 @@
1950
1994
  "cv.review_link"
1951
1995
  ]
1952
1996
  },
1997
+ {
1998
+ "ref": "handoff.review_tag",
1999
+ "conditionalOn": "goal.bookingNeeded == true && goal.reviewTiming == 'When you mark the work done'",
2000
+ "owner": "OPERATOR-UI",
2001
+ "title": "Start Review Request by tagging service-complete when the work is done",
2002
+ "trigger": "always (1 minute to read)",
2003
+ "instruction": "The intake chose to ask for a review when the work is done, so Review Request starts from the tag service-complete, not from a visit. Add that tag on the contact when their care is finished. Each patient is asked for a review once.",
2004
+ "successCheck": "A contact tagged service-complete receives the review text two hours later"
2005
+ },
1953
2006
  {
1954
2007
  "ref": "handoff.quote_tag",
1955
2008
  "owner": "OPERATOR-UI",
@@ -55,18 +55,18 @@
55
55
  },
56
56
  {
57
57
  "ref": "stage.contacted",
58
- "aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up"],
58
+ "aliases": ["contacted", "engaged", "in conversation", "qualified", "follow up", "working"],
59
59
  "name": "Contacted"
60
60
  },
61
61
  {
62
62
  "ref": "stage.call_booked",
63
- "aliases": ["call booked", "discovery call booked", "strategy call booked", "consult booked", "appointment booked", "booked", "scheduled"],
63
+ "aliases": ["call booked", "discovery call booked", "strategy call booked", "consult booked", "appointment booked", "booked", "scheduled", "=consultation"],
64
64
  "name": "Call Booked",
65
65
  "conditionalOn": "goal.bookingNeeded == true"
66
66
  },
67
67
  {
68
68
  "ref": "stage.call_held",
69
- "aliases": ["call held", "call completed", "showed", "attended"],
69
+ "aliases": ["call held", "call completed", "showed", "attended", "consultation completed", "consultation done", "consultation attended", "consultation showed", "consultation complete", "consultation held"],
70
70
  "name": "Call Held",
71
71
  "conditionalOn": "goal.bookingNeeded == true"
72
72
  },
@@ -274,7 +274,7 @@
274
274
  {
275
275
  "ref": "tag.service_done",
276
276
  "name": "service-complete",
277
- "_note": "Added when the work is done and paid for. Starts Review Request; removed by it so the next job can start it again."
277
+ "_note": "Added when the work is done and paid for. Starts Review Request; removed by it. Each contact is asked for a review once."
278
278
  }
279
279
  ],
280
280
  "_tagNote": "Interest tags mirror the Program Interest options; the skill renames them to the real program names from the offer at fill time.",
@@ -1073,6 +1073,7 @@
1073
1073
  "workflows": [
1074
1074
  {
1075
1075
  "ref": "workflow.speed_to_lead",
1076
+ "allowMultiple": false,
1076
1077
  "name": "Speed to Lead",
1077
1078
  "trigger": {
1078
1079
  "type": "form_submission",
@@ -1158,6 +1159,7 @@
1158
1159
  },
1159
1160
  {
1160
1161
  "ref": "workflow.missed_call",
1162
+ "allowMultiple": true,
1161
1163
  "name": "Missed Call Text-Back",
1162
1164
  "conditionalOn": "channels.sms == true",
1163
1165
  "trigger": {
@@ -1212,13 +1214,14 @@
1212
1214
  },
1213
1215
  {
1214
1216
  "ref": "workflow.lead_nurture",
1217
+ "allowMultiple": false,
1215
1218
  "name": "Lead Nurture (30 days)",
1216
1219
  "trigger": {
1217
1220
  "type": "contact_tag",
1218
1221
  "tagRef": "tag.nurture_start"
1219
1222
  },
1220
1223
  "stopOnResponse": true,
1221
- "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first so a later hand-off can start it again. Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1224
+ "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1222
1225
  "actions": [
1223
1226
  {
1224
1227
  "type": "remove_contact_tag",
@@ -1353,6 +1356,7 @@
1353
1356
  },
1354
1357
  {
1355
1358
  "ref": "workflow.reply_route",
1359
+ "allowMultiple": true,
1356
1360
  "name": "Replied - Stop & Route",
1357
1361
  "trigger": {
1358
1362
  "type": "customer_reply"
@@ -1407,6 +1411,7 @@
1407
1411
  },
1408
1412
  {
1409
1413
  "ref": "workflow.win_back",
1414
+ "allowMultiple": false,
1410
1415
  "name": "Win-back (30 days)",
1411
1416
  "trigger": {
1412
1417
  "type": "contact_tag",
@@ -1478,6 +1483,7 @@
1478
1483
  },
1479
1484
  {
1480
1485
  "ref": "workflow.appointment_booked",
1486
+ "allowMultiple": true,
1481
1487
  "name": "Call Booked - Confirm & Move",
1482
1488
  "trigger": {
1483
1489
  "type": "appointment",
@@ -1499,6 +1505,15 @@
1499
1505
  "type": "remove_from_workflow",
1500
1506
  "workflowRef": "workflow.win_back"
1501
1507
  },
1508
+ {
1509
+ "type": "remove_from_workflow",
1510
+ "workflowRef": "workflow.no_show"
1511
+ },
1512
+ {
1513
+ "type": "remove_from_workflow",
1514
+ "workflowRef": "workflow.missed_call",
1515
+ "conditionalOn": "channels.sms == true"
1516
+ },
1502
1517
  {
1503
1518
  "type": "add_contact_tag",
1504
1519
  "tagRef": "tag.lc_booked"
@@ -1542,6 +1557,7 @@
1542
1557
  },
1543
1558
  {
1544
1559
  "ref": "workflow.appointment_reminders",
1560
+ "allowMultiple": true,
1545
1561
  "name": "Call Reminders",
1546
1562
  "trigger": {
1547
1563
  "type": "appointment",
@@ -1580,6 +1596,7 @@
1580
1596
  },
1581
1597
  {
1582
1598
  "ref": "workflow.no_show",
1599
+ "allowMultiple": true,
1583
1600
  "stagePreference": { "stage.contacted": ["no show", "noshow", "missed appointment", "did not show"] },
1584
1601
  "name": "No-Show Rescue",
1585
1602
  "trigger": {
@@ -1669,6 +1686,7 @@
1669
1686
  },
1670
1687
  {
1671
1688
  "ref": "workflow.quote_follow_up",
1689
+ "allowMultiple": true,
1672
1690
  "name": "Quote Follow-up",
1673
1691
  "trigger": {
1674
1692
  "type": "contact_tag",
@@ -1730,6 +1748,7 @@
1730
1748
  },
1731
1749
  {
1732
1750
  "ref": "workflow.review_request",
1751
+ "allowMultiple": false,
1733
1752
  "name": "Review Request",
1734
1753
  "trigger": {
1735
1754
  "type": "contact_tag",
@@ -814,6 +814,7 @@
814
814
  "workflows": [
815
815
  {
816
816
  "ref": "workflow.speed_to_lead",
817
+ "allowMultiple": false,
817
818
  "name": "Speed to Lead",
818
819
  "trigger": {
819
820
  "type": "form_submission",
@@ -899,6 +900,7 @@
899
900
  },
900
901
  {
901
902
  "ref": "workflow.missed_call",
903
+ "allowMultiple": true,
902
904
  "name": "Missed Call Text-Back",
903
905
  "conditionalOn": "channels.sms == true",
904
906
  "trigger": {
@@ -953,13 +955,14 @@
953
955
  },
954
956
  {
955
957
  "ref": "workflow.lead_nurture",
958
+ "allowMultiple": false,
956
959
  "name": "Lead Nurture (30 days)",
957
960
  "trigger": {
958
961
  "type": "contact_tag",
959
962
  "tagRef": "tag.nurture_start"
960
963
  },
961
964
  "stopOnResponse": true,
962
- "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first so a later hand-off can start it again. Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
965
+ "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
963
966
  "actions": [
964
967
  {
965
968
  "type": "remove_contact_tag",
@@ -1094,6 +1097,7 @@
1094
1097
  },
1095
1098
  {
1096
1099
  "ref": "workflow.reply_route",
1100
+ "allowMultiple": true,
1097
1101
  "name": "Replied - Stop & Route",
1098
1102
  "trigger": {
1099
1103
  "type": "customer_reply"
@@ -1143,6 +1147,7 @@
1143
1147
  },
1144
1148
  {
1145
1149
  "ref": "workflow.win_back",
1150
+ "allowMultiple": false,
1146
1151
  "name": "Win-back (30 days)",
1147
1152
  "trigger": {
1148
1153
  "type": "contact_tag",
@@ -1214,6 +1219,7 @@
1214
1219
  },
1215
1220
  {
1216
1221
  "ref": "workflow.review_request",
1222
+ "allowMultiple": false,
1217
1223
  "name": "Review Requests",
1218
1224
  "trigger": {
1219
1225
  "type": "contact_tag",
@@ -1359,7 +1365,7 @@
1359
1365
  "owner": "OPERATOR-EXT",
1360
1366
  "title": "Connect store orders to GHL",
1361
1367
  "trigger": "always",
1362
- "instruction": "Connect your store so orders reach GHL (Shopify: the native integration under Integrations; other platforms: an order webhook to an inbound webhook trigger). Then add a small workflow: on order → tag lifecycle-first-order, remove from Lead Nurture and Win-back, move the pipeline card to First Order. Until this exists, staff move cards by hand.",
1368
+ "instruction": "Connect your store so orders reach GHL (Shopify: the native integration under Integrations; other platforms: an order webhook to an inbound webhook trigger). Then add a small workflow: on order → tag lifecycle-first-order, remove from Lead Nurture and Win-back, move the pipeline card to {{stage.first_order}}. Until this exists, staff move cards by hand.",
1363
1369
  "produces": null,
1364
1370
  "successCheck": "A test order appears on the contact in GHL within a minute",
1365
1371
  "blocks": []
@@ -49,7 +49,7 @@
49
49
  },
50
50
  {
51
51
  "ref": "stage.contacted",
52
- "aliases": ["contacted", "engaged", "in conversation", "follow up"],
52
+ "aliases": ["contacted", "engaged", "in conversation", "follow up", "working"],
53
53
  "name": "Contacted"
54
54
  },
55
55
  {
@@ -59,7 +59,7 @@
59
59
  },
60
60
  {
61
61
  "ref": "stage.appointment_booked",
62
- "aliases": ["appointment booked", "consult booked", "consultation booked", "call booked", "booked", "scheduled"],
62
+ "aliases": ["appointment booked", "consult booked", "consultation booked", "call booked", "booked", "scheduled", "=consultation"],
63
63
  "name": "Appointment Booked",
64
64
  "conditionalOn": "goal.bookingNeeded == true"
65
65
  },
@@ -1070,6 +1070,7 @@
1070
1070
  "workflows": [
1071
1071
  {
1072
1072
  "ref": "workflow.speed_to_lead",
1073
+ "allowMultiple": false,
1073
1074
  "name": "Speed to Lead",
1074
1075
  "trigger": {
1075
1076
  "type": "form_submission",
@@ -1155,6 +1156,7 @@
1155
1156
  },
1156
1157
  {
1157
1158
  "ref": "workflow.missed_call",
1159
+ "allowMultiple": true,
1158
1160
  "name": "Missed Call Text-Back",
1159
1161
  "conditionalOn": "channels.sms == true",
1160
1162
  "trigger": {
@@ -1209,13 +1211,14 @@
1209
1211
  },
1210
1212
  {
1211
1213
  "ref": "workflow.lead_nurture",
1214
+ "allowMultiple": false,
1212
1215
  "name": "Lead Nurture (30 days)",
1213
1216
  "trigger": {
1214
1217
  "type": "contact_tag",
1215
1218
  "tagRef": "tag.nurture_start"
1216
1219
  },
1217
1220
  "stopOnResponse": true,
1218
- "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first so a later hand-off can start it again. Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1221
+ "_design": "Starts from the Speed to Lead hand-off tag. 11 touches (6 emails + 5 texts) across 31 days of waits: give, give, ask. Removes its own trigger tag first; each contact goes through it once (re-entry off). Ends by tagging lapsed and handing off to Win-back. Any reply stops it (stopOnResponse) and Replied - Stop & Route takes over.",
1219
1222
  "actions": [
1220
1223
  {
1221
1224
  "type": "remove_contact_tag",
@@ -1350,6 +1353,7 @@
1350
1353
  },
1351
1354
  {
1352
1355
  "ref": "workflow.reply_route",
1356
+ "allowMultiple": true,
1353
1357
  "name": "Replied - Stop & Route",
1354
1358
  "trigger": {
1355
1359
  "type": "customer_reply"
@@ -1404,6 +1408,7 @@
1404
1408
  },
1405
1409
  {
1406
1410
  "ref": "workflow.win_back",
1411
+ "allowMultiple": false,
1407
1412
  "name": "Win-back (30 days)",
1408
1413
  "trigger": {
1409
1414
  "type": "contact_tag",
@@ -1475,6 +1480,7 @@
1475
1480
  },
1476
1481
  {
1477
1482
  "ref": "workflow.appointment_booked",
1483
+ "allowMultiple": true,
1478
1484
  "name": "Appointment Booked - Confirm & Move",
1479
1485
  "trigger": {
1480
1486
  "type": "appointment",
@@ -1496,6 +1502,15 @@
1496
1502
  "type": "remove_from_workflow",
1497
1503
  "workflowRef": "workflow.win_back"
1498
1504
  },
1505
+ {
1506
+ "type": "remove_from_workflow",
1507
+ "workflowRef": "workflow.no_show"
1508
+ },
1509
+ {
1510
+ "type": "remove_from_workflow",
1511
+ "workflowRef": "workflow.missed_call",
1512
+ "conditionalOn": "channels.sms == true"
1513
+ },
1499
1514
  {
1500
1515
  "type": "add_contact_tag",
1501
1516
  "tagRef": "tag.lc_booked"
@@ -1539,6 +1554,7 @@
1539
1554
  },
1540
1555
  {
1541
1556
  "ref": "workflow.appointment_reminders",
1557
+ "allowMultiple": true,
1542
1558
  "name": "Appointment Reminders",
1543
1559
  "trigger": {
1544
1560
  "type": "appointment",
@@ -1577,6 +1593,7 @@
1577
1593
  },
1578
1594
  {
1579
1595
  "ref": "workflow.no_show",
1596
+ "allowMultiple": true,
1580
1597
  "stagePreference": { "stage.contacted": ["no show", "noshow", "missed appointment", "did not show"] },
1581
1598
  "name": "No-Show Rescue",
1582
1599
  "trigger": {
@@ -1666,6 +1683,7 @@
1666
1683
  },
1667
1684
  {
1668
1685
  "ref": "workflow.review_request",
1686
+ "allowMultiple": false,
1669
1687
  "name": "Review Requests",
1670
1688
  "trigger": {
1671
1689
  "type": "contact_tag",
@@ -1715,6 +1733,7 @@
1715
1733
  },
1716
1734
  {
1717
1735
  "ref": "workflow.quote_follow_up",
1736
+ "allowMultiple": true,
1718
1737
  "name": "Quote Follow-up",
1719
1738
  "trigger": {
1720
1739
  "type": "contact_tag",