@elitedcs/ghl-mcp 3.72.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 CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.72.1 — a client's work stops shipping in the package
4
+
5
+ An audit of the published 3.72.0 tarball found somebody else's proprietary material
6
+ inside it. Not a leak of customer data, and nobody did it on purpose: the packaging
7
+ allow-list named `skills` as a whole directory, so every file that landed in that folder
8
+ published itself along with the release.
9
+
10
+ What was in there. A launch-event preset built from a partner agency's own client
11
+ blueprint, their compliance playbook and a protected live account: ten named workflows,
12
+ twenty-five custom values, an event calendar and the whole compliance handoff chain.
13
+ Three more presets credited real client accounts by name in their provenance lines. The
14
+ compiled bundle carried a real GoHighLevel account id with a label identifying whose it
15
+ was, and one shipped example held a real sandbox id.
16
+
17
+ That preset stays in the source tree, because it is a legitimate tool for the agency that
18
+ wrote it. It is now excluded from what gets published. Every client and partner name is
19
+ out of the published files, and the two account lists that used to be compiled into the
20
+ bundle now come from `account-rules.json` in your own application-support directory. A
21
+ fresh install declares no accounts at all, which is the stricter default: without a
22
+ declared practice account every account needs an explicit recorded approval before
23
+ anything is built, and an account you mark off-limits is refused even when an approval
24
+ exists.
25
+
26
+ Engineering comments recording where a workflow shape was captured are untouched. They
27
+ live in source that is never published, and they are how the next person knows a node
28
+ shape came from a real account rather than a guess.
29
+
30
+ A new check reads the actual file list `npm pack` would publish and fails the build if a
31
+ client name, a real-looking account id, or a file on the never-publish list appears in it.
32
+ It caught two files that a careful pass by hand had already missed.
33
+
34
+ Counts unchanged: 247 tools, 51 modules, free tier 111. No tool behaviour changed.
35
+
36
+
3
37
  ## 3.72.0 — an audit that counts your workflows once
4
38
 
5
39
  A member ran the account audit on a large agency account. The report came back claiming
@@ -2494,7 +2528,7 @@ Two bugs caught when exercising v3.4.0's `health_check` and `validate_workflow`
2494
2528
  Also: `src/index.ts` startup `validateApiKey()` had the same wrong-endpoint bug as `health_check`; fixed for consistency. Buyers will now see "API key validated" or a clear 403 message on startup instead of silent ambiguity.
2495
2529
 
2496
2530
  ### Verified end-to-end
2497
- Re-ran the spawn-handshake test against QA Test Clinic v3:
2531
+ Re-ran the spawn-handshake test against a protected account:
2498
2532
  - `health_check` returns 5/5 PASS ("All systems go.")
2499
2533
  - `validate_workflow` on the CLL Onboarding Form Submitted workflow correctly scans 2 references (form.id + self-referencing workflow.id), reports 0 issues, surfaces the self-reference as an informational note.
2500
2534
 
@@ -2634,7 +2668,7 @@ For Claude that means: when you ask it to read, build, or edit a workflow using
2634
2668
  | v3.2.0 | **13** | **44** | 57 |
2635
2669
 
2636
2670
  ### Round-trip verified
2637
- - Real `form_submission` workflow from QA Test Clinic v3 parses cleanly through the new typed variant.
2671
+ - Real `form_submission` workflow from a protected account parses cleanly through the new typed variant.
2638
2672
  - 8 synthetic typed-trigger samples match their respective variants and are correctly rejected by sibling variants (discrimination works as expected).
2639
2673
 
2640
2674
  ### Files changed
package/dist/index.js CHANGED
@@ -17875,7 +17875,7 @@ var init_plan = __esm({
17875
17875
  // Only works when the workflow has an appointment in context (i.e. an
17876
17876
  // `appointment` trigger) — enforced by validateBuildPlan. Expands to GHL's
17877
17877
  // appointment-wait node shape (attributes.type "appointment" +
17878
- // appointmentStartAfter, captured live from Lux Bio). Integer units only
17878
+ // appointmentStartAfter, captured live from a real account). Integer units only
17879
17879
  // (GHL stores whole minutes). Only "before" is emitted today — that's the
17880
17880
  // shape we captured + proved; "after" (post-appointment follow-up) is
17881
17881
  // deferred until its shape is captured from a real workflow.
@@ -23213,7 +23213,7 @@ var require_package = __commonJS({
23213
23213
  "package.json"(exports2, module2) {
23214
23214
  module2.exports = {
23215
23215
  name: "@elitedcs/ghl-mcp",
23216
- version: "3.72.0",
23216
+ version: "3.72.1",
23217
23217
  mcpName: "io.github.drjerryrelth/ghl-command",
23218
23218
  description: "GoHighLevel MCP Server for Claude. 247 tools \u2014 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.",
23219
23219
  main: "dist/index.js",
@@ -23230,13 +23230,10 @@ var require_package = __commonJS({
23230
23230
  "templates/external-funnel/README.md",
23231
23231
  "README.md",
23232
23232
  "CHANGELOG.md",
23233
- "skills",
23234
23233
  "guide",
23235
- "skills/blueprint/references/build-plan-schema.md",
23236
- "skills/blueprint/presets/generic-client.preset.json",
23237
- "skills/blueprint/presets/med-spa.preset.json",
23238
- "skills/blueprint/presets/clinic-launch-a2p.preset.json",
23239
- "skills/blueprint/examples/sample-build-plan.json"
23234
+ "skills",
23235
+ "!skills/blueprint/presets/clinic-launch-a2p.preset.json",
23236
+ "!skills/blueprint/presets/clinic-launch-a2p.md"
23240
23237
  ],
23241
23238
  scripts: {
23242
23239
  build: "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.js --packages=external && esbuild src/capture-helper.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/capture-helper.js --packages=external",
@@ -23570,6 +23567,24 @@ var init_stage_boundary = __esm({
23570
23567
  });
23571
23568
 
23572
23569
  // src/stage-runner.ts
23570
+ function accountRulesPath() {
23571
+ const override = process.env[ACCOUNT_RULES_ENV];
23572
+ return override && override.trim() ? override : path18.join(appDataDir(), "account-rules.json");
23573
+ }
23574
+ function readAccountRules(file = accountRulesPath()) {
23575
+ try {
23576
+ const raw = JSON.parse(fs19.readFileSync(file, "utf8"));
23577
+ const pick = (v) => {
23578
+ if (!v || typeof v !== "object" || Array.isArray(v)) return {};
23579
+ return Object.fromEntries(
23580
+ Object.entries(v).filter(([k, name]) => typeof k === "string" && k.length > 0 && typeof name === "string").map(([k, name]) => [k, name])
23581
+ );
23582
+ };
23583
+ return { sandboxes: pick(raw.sandboxes), protected: pick(raw.protected) };
23584
+ } catch {
23585
+ return EMPTY_RULES;
23586
+ }
23587
+ }
23573
23588
  function buildPermission(locationId2, opts = {}) {
23574
23589
  const protectedName = PROTECTED_LOCATIONS[locationId2];
23575
23590
  if (protectedName) {
@@ -23762,7 +23777,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
23762
23777
  });
23763
23778
  });
23764
23779
  }
23765
- var import_child_process3, fs19, os6, path18, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
23780
+ var import_child_process3, fs19, os6, path18, EMPTY_RULES, ACCOUNT_RULES_ENV, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
23766
23781
  var init_stage_runner = __esm({
23767
23782
  "src/stage-runner.ts"() {
23768
23783
  "use strict";
@@ -23770,18 +23785,32 @@ var init_stage_runner = __esm({
23770
23785
  fs19 = __toESM(require("fs"));
23771
23786
  os6 = __toESM(require("os"));
23772
23787
  path18 = __toESM(require("path"));
23788
+ init_credentials_store();
23773
23789
  init_stage_boundary();
23774
23790
  init_plan_guide();
23775
23791
  init_question_set();
23776
23792
  init_build_progress();
23777
23793
  init_build_journal();
23778
- SANDBOX_ALLOWLIST = {
23779
- JrV2p35O3hY2wqhr2c0T: "MCP Testing",
23780
- jHP5wkYRineXDlzAOEbW: "Blueprint Demo"
23781
- };
23782
- PROTECTED_LOCATIONS = {
23783
- HJfJXQdWwPNxYdjqI1ua: "QA Test Clinic - v3 (live CLL snapshot source)"
23784
- };
23794
+ EMPTY_RULES = { sandboxes: {}, protected: {} };
23795
+ ACCOUNT_RULES_ENV = "GHL_MCP_ACCOUNT_RULES";
23796
+ SANDBOX_ALLOWLIST = new Proxy({}, {
23797
+ get: (_t, k) => readAccountRules().sandboxes[k],
23798
+ has: (_t, k) => k in readAccountRules().sandboxes,
23799
+ ownKeys: () => Reflect.ownKeys(readAccountRules().sandboxes),
23800
+ getOwnPropertyDescriptor: (_t, k) => {
23801
+ const v = readAccountRules().sandboxes[k];
23802
+ return v === void 0 ? void 0 : { value: v, enumerable: true, configurable: true };
23803
+ }
23804
+ });
23805
+ PROTECTED_LOCATIONS = new Proxy({}, {
23806
+ get: (_t, k) => readAccountRules().protected[k],
23807
+ has: (_t, k) => k in readAccountRules().protected,
23808
+ ownKeys: () => Reflect.ownKeys(readAccountRules().protected),
23809
+ getOwnPropertyDescriptor: (_t, k) => {
23810
+ const v = readAccountRules().protected[k];
23811
+ return v === void 0 ? void 0 : { value: v, enumerable: true, configurable: true };
23812
+ }
23813
+ });
23785
23814
  STAFF_NOTIFICATION_RULE = `Staff notifications ALWAYS stay in the workflow. Call get_users on THIS account and use only a user id it returns as the recipient. If get_users returns no users, leave the recipient EMPTY ("") \u2014 never use a user id from another account, from the agency, from an earlier conversation or from memory (a foreign id looks like it worked and notifies nobody). An empty recipient can make publish fail: then keep the workflow as a draft with the notification step still in it, and list that step in the RESULT issues as "waiting for a staff member: add one in Settings \u2192 My Staff, then pick them on the notification step and publish".`;
23786
23815
  MISSING_STEP_RULE = `A step the plan asks for that is not in the account is NEVER dropped from your report. If apply_build_plan answers with a non-empty missingSteps, put EVERY one of those steps in issues, naming the workflow and the step, and say the build is not finished. A later run reporting "refreshed in place" does not close a gap an earlier run reported \u2014 only the step actually being in the workflow does. Never call a build complete while missingSteps has anything in it.`;
23787
23816
  NO_INVENTED_CAUSE_RULE = `NEVER invent a reason a tool failed, and never state an HTTP status code a tool did not actually return. When a tool answers with an explanation of its own \u2014 an "errorKind", a "next", or a plain-English reason \u2014 quote THAT, verbatim, as the cause, and stop there. Do not add your own diagnosis on top of it, and do not guess at credentials, scopes or permissions: those guesses read as fact to the person acting on them and send them days in the wrong direction. If a tool failed and told you nothing useful, say exactly that \u2014 "it failed and did not say why" \u2014 rather than filling the gap.`;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.72.0",
3
+ "version": "3.72.1",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
- "description": "GoHighLevel MCP Server for Claude. 247 tools \u2014 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.",
5
+ "description": "GoHighLevel MCP Server for Claude. 247 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",
7
7
  "bin": {
8
8
  "ghl-mcp": "dist/index.js"
@@ -17,13 +17,10 @@
17
17
  "templates/external-funnel/README.md",
18
18
  "README.md",
19
19
  "CHANGELOG.md",
20
- "skills",
21
20
  "guide",
22
- "skills/blueprint/references/build-plan-schema.md",
23
- "skills/blueprint/presets/generic-client.preset.json",
24
- "skills/blueprint/presets/med-spa.preset.json",
25
- "skills/blueprint/presets/clinic-launch-a2p.preset.json",
26
- "skills/blueprint/examples/sample-build-plan.json"
21
+ "skills",
22
+ "!skills/blueprint/presets/clinic-launch-a2p.preset.json",
23
+ "!skills/blueprint/presets/clinic-launch-a2p.md"
27
24
  ],
28
25
  "scripts": {
29
26
  "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.js --packages=external && esbuild src/capture-helper.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/capture-helper.js --packages=external",
@@ -4,7 +4,7 @@
4
4
 
5
5
  This is the live proof that the med_spa preset produces a plan that passes the MCP's
6
6
  authoritative validator and dry-runs clean. Captured 2026-06-25 against the **MCP Testing**
7
- sandbox (`JrV2p35O3hY2wqhr2c0T`, a throwaway 1-user account). dry_run writes nothing.
7
+ sandbox (a throwaway 1-user account). dry_run writes nothing.
8
8
 
9
9
  Source artifacts: [`medspa-brief.json`](medspa-brief.json) → [`medspa-build-plan.json`](medspa-build-plan.json).
10
10
 
@@ -1,7 +1,7 @@
1
1
  # Preset: Clinic / Practice
2
2
 
3
3
  **File:** `clinic.preset.json` · **id:** `clinic` · **version:** 2.0.0 · **default:** no
4
- **Source:** Generalized from the proven clinic builds (Lux Bio Therapy, QA Test Clinic patterns) with the intake industry pack "clinic" (insurance_or_cash, new_patient_offer, compliance_notes); v2 depth per the owner review of 2026-08-26.
4
+ **Source:** Generalized from proven clinic builds with the intake industry pack "clinic" (insurance_or_cash, new_patient_offer, compliance_notes); v2 depth per the owner review of 2026-08-26.
5
5
 
6
6
  ## When it is selected
7
7
  Routes from the "Clinic / practice" business type and the `clinic` / `practice` / `chiro` / `dental` / `pt` / `functional_medicine` aliases. Med spas route to `med_spa`; a time-boxed launch event routes to `clinic_launch_a2p`.
@@ -25,7 +25,7 @@
25
25
  "functional_medicine"
26
26
  ]
27
27
  },
28
- "source": "Generalized from the proven clinic builds (Lux Bio Therapy, QA Test Clinic patterns) with the intake industry pack \"clinic\" (insurance_or_cash, new_patient_offer, compliance_notes); v2 depth per the owner review of 2026-08-26.",
28
+ "source": "Generalized from proven clinic builds with the intake industry pack \"clinic\" (insurance_or_cash, new_patient_offer, compliance_notes); v2 depth per the owner review of 2026-08-26.",
29
29
  "conventions": {
30
30
  "triggerTokens": "Use ONLY the executor's native trigger types so triggers auto-build: contact_tag (tagRef), form_submission (formRef), appointment (appointmentStatus: confirmed|noshow|...; calendarRef optional), customer_reply, pipeline_stage_updated (pipelineRef+stageRef), call_status (callStatuses + callDirection; the missed-call text-back), inbound_webhook, payment_received. Never tag_added / form_submitted / appointment_status / appointment_booked / contact_replied.",
31
31
  "handoffTag": "Sequences hand off with add_contact_tag + a contact_tag trigger (nurture-start, winback-start), NOT add_to_workflow. Both halves of the tag hand-off are live-proven action shapes (the add-tag node and the tag trigger are captured from working GHL UI-built workflows in templates/action-schemas.json); add_to_workflow has no captured native shape in action-schemas.json and has not been proven at runtime, so a preset may not use it. Each sequence removes its own trigger tag as its first action so a later hand-off can fire it again.",
@@ -1,7 +1,7 @@
1
1
  # Preset: Generic Client
2
2
 
3
3
  **File:** `generic-client.preset.json` · **id:** `generic` · **version:** 2.0.0 · **default:** no
4
- **Source:** ghl-command showcase-video Modules 0-7 + the proven Bliss Med Spa build; v2 depth per the owner review of 2026-08-26.
4
+ **Source:** ghl-command showcase-video Modules 0-7 + a proven med-spa build; v2 depth per the owner review of 2026-08-26.
5
5
 
6
6
  ## When it is selected
7
7
  The fallback for every `business_type` without a dedicated preset: agencies, "other", and anything unmatched. Med spa, clinic, coach, ecommerce and local service each route to their own preset now; this one carries the same workflow depth with neutral copy.
@@ -16,7 +16,7 @@
16
16
  "other"
17
17
  ]
18
18
  },
19
- "source": "ghl-command showcase-video Modules 0-7 + the proven Bliss Med Spa build; v2 depth per the owner review of 2026-08-26.",
19
+ "source": "ghl-command showcase-video Modules 0-7 + a proven med-spa build; v2 depth per the owner review of 2026-08-26.",
20
20
  "conventions": {
21
21
  "triggerTokens": "Use ONLY the executor's native trigger types so triggers auto-build: contact_tag (tagRef), form_submission (formRef), appointment (appointmentStatus: confirmed|noshow|...; calendarRef optional), customer_reply, pipeline_stage_updated (pipelineRef+stageRef), call_status (callStatuses + callDirection; the missed-call text-back), inbound_webhook, payment_received. Never tag_added / form_submitted / appointment_status / appointment_booked / contact_replied.",
22
22
  "handoffTag": "Sequences hand off with add_contact_tag + a contact_tag trigger (nurture-start, winback-start), NOT add_to_workflow. Both halves of the tag hand-off are live-proven action shapes (the add-tag node and the tag trigger are captured from working GHL UI-built workflows in templates/action-schemas.json); add_to_workflow has no captured native shape in action-schemas.json and has not been proven at runtime, so a preset may not use it. Each sequence removes its own trigger tag as its first action so a later hand-off can fire it again.",
@@ -1,7 +1,7 @@
1
1
  # Preset: Med Spa (default)
2
2
 
3
3
  **File:** `med-spa.preset.json` · **id:** `med_spa` · **version:** 2.0.0 · **default:** yes
4
- **Source:** ghl-command showcase Modules 0-7 + the proven Bliss Med Spa / Lux Bio Therapy builds; v2 depth per the owner review of 2026-08-26 (every workflow must do real work; nurture never under 30 days; copy to the Gary V / Hormozi standard in references/copy-guide.md).
4
+ **Source:** ghl-command showcase Modules 0-7 + proven med-spa builds; v2 depth per the owner review of 2026-08-26 (every workflow must do real work; nurture never under 30 days; copy to the Gary V / Hormozi standard in references/copy-guide.md).
5
5
 
6
6
  ## When it is selected
7
7
  The default reference preset, and the routing target for med-spa / aesthetics business types (`selectors.businessTypes`) and the `med_spa` / `medspa` / `aesthetics` / `injectables` aliases. A brief whose `business_type` matches no preset resolves here.
@@ -21,7 +21,7 @@
21
21
  "injectables"
22
22
  ]
23
23
  },
24
- "source": "ghl-command showcase Modules 0-7 + the proven Bliss Med Spa / Lux Bio Therapy builds; v2 depth per the owner review of 2026-08-26 (every workflow must do real work; nurture never under 30 days; copy to the Gary V / Hormozi standard in references/copy-guide.md).",
24
+ "source": "ghl-command showcase Modules 0-7 + proven med-spa builds; v2 depth per the owner review of 2026-08-26 (every workflow must do real work; nurture never under 30 days; copy to the Gary V / Hormozi standard in references/copy-guide.md).",
25
25
  "conventions": {
26
26
  "triggerTokens": "Use ONLY the executor's native trigger types so triggers auto-build: contact_tag (tagRef), form_submission (formRef), appointment (appointmentStatus: confirmed|noshow|...; calendarRef optional), customer_reply, pipeline_stage_updated (pipelineRef+stageRef), call_status (callStatuses + callDirection; the missed-call text-back), inbound_webhook, payment_received. Never tag_added / form_submitted / appointment_status / appointment_booked / contact_replied.",
27
27
  "handoffTag": "Sequences hand off with add_contact_tag + a contact_tag trigger (nurture-start, winback-start), NOT add_to_workflow. Both halves of the tag hand-off are live-proven action shapes (the add-tag node and the tag trigger are captured from working GHL UI-built workflows in templates/action-schemas.json); add_to_workflow has no captured native shape in action-schemas.json and has not been proven at runtime, so a preset may not use it. Each sequence removes its own trigger tag as its first action so a later hand-off can fire it again.",
@@ -32,7 +32,7 @@ Use the §3 question set, and surface the credit line once:
32
32
 
33
33
  ## 4. Ingestion mapping — Agency OS artifacts → Brief (§4)
34
34
 
35
- The artifact files are markdown with predictable headings (validated against real instances in Jerry's `clinic-launch-lab/brand/` and `_shared/`). Parse by section heading; carry the deep structures **verbatim** into the brief's `extended` block so plan-gen uses full psychographics, not a flattened summary. Set `briefSource: "agency_os"` (or `"hybrid"` if you mix in form answers for channel/tech facts Agency OS does not capture).
35
+ The artifact files are markdown with predictable headings (validated against real instances in Jerry's `client-workspace/brand/` and `_shared/`). Parse by section heading; carry the deep structures **verbatim** into the brief's `extended` block so plan-gen uses full psychographics, not a flattened summary. Set `briefSource: "agency_os"` (or `"hybrid"` if you mix in form answers for channel/tech facts Agency OS does not capture).
36
36
 
37
37
  ### `owner-profile.md` (brand-dna / build-profile) → brief
38
38
  | Section in owner-profile.md | Brief field |
@@ -9,7 +9,7 @@
9
9
  "question": "What is the name of your clinic or med spa?",
10
10
  "type": "text",
11
11
  "required": true,
12
- "placeholder": "e.g. Desert Body Contour"
12
+ "placeholder": "e.g. Bright Path Wellness"
13
13
  },
14
14
  {
15
15
  "id": "owner_name",
@@ -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.
@@ -1,338 +0,0 @@
1
- {
2
- "presetId": "clinic_launch_a2p",
3
- "presetVersion": "1.0.0",
4
- "schemaVersion": "0.1",
5
- "title": "Clinic Launch Event + A2P",
6
- "description": "The database-reactivation launch-event build (clinic / med spa on-site events): a 15-stage DBR pipeline, intake/consent fields, the launch custom-value set, the DBR SMS line + booking + sale workflow system, an event calendar with deposit, and the full compliance handoff chain (A2P site, A2P brand + campaign, Stripe products + links, phone purchase, Conversation AI prompts, SMS warming). NOT the default; selected explicitly for a launch-event / database-reactivation engagement. Heavy on operator handoffs by design.",
7
- "default": false,
8
- "selectors": {
9
- "businessTypes": [],
10
- "aliases": ["clinic_launch_a2p", "cll", "dbr", "launch_event"],
11
- "selectBy": "Chosen explicitly, or when the brief describes a time-boxed on-site event / database-reactivation model (event_start/end dates, deposit + balance, package tiers, SMS reactivation of an existing list). Not auto-selected by business_type."
12
- },
13
- "source": "command-center/shared/client-provisioning-spec.md + clinic-launch-lab CLIENT_BUILD_AUTOMATION_BLUEPRINT.md + A2P_Client_Playbook.md + QA v3 snapshot",
14
- "skeleton": {
15
- "pipelines": [
16
- {
17
- "ref": "pipeline.launch",
18
- "name": "{{business.name}} Launch Events || Launch Events",
19
- "stages": {
20
- "fillFrom": "goal.salesStages",
21
- "transform": "stageList",
22
- "default": [
23
- { "ref": "stage.dbr_lead", "name": "DBR Lead", "position": 0 },
24
- { "ref": "stage.active_dbr", "name": "Active In DBR", "position": 1 },
25
- { "ref": "stage.replied_dbr", "name": "Replied To DBR", "position": 2 },
26
- { "ref": "stage.completed_dbr", "name": "Completed DBR", "position": 3 },
27
- { "ref": "stage.booking_link_sent", "name": "Interested - Booking Link Sent", "position": 4 },
28
- { "ref": "stage.deposit_paid", "name": "Booked - Deposit Paid", "position": 5 },
29
- { "ref": "stage.showed", "name": "Appointment Showed", "position": 6 },
30
- { "ref": "stage.package_sold", "name": "Package Sold", "position": 7 },
31
- { "ref": "stage.no_sale", "name": "No Package Sold - Follow Up", "position": 8 },
32
- { "ref": "stage.no_show", "name": "Appointment No Show", "position": 9 },
33
- { "ref": "stage.canceled", "name": "Appointment Canceled", "position": 10 },
34
- { "ref": "stage.rescheduled", "name": "Rescheduled", "position": 11 },
35
- { "ref": "stage.not_interested", "name": "Not Interested", "position": 12 },
36
- { "ref": "stage.dnd", "name": "DND", "position": 13 },
37
- { "ref": "stage.lost", "name": "Lost", "position": 14 }
38
- ]
39
- }
40
- }
41
- ],
42
- "customFields": [
43
- { "ref": "field.package_recommended", "name": "Package Recommended", "dataType": "SINGLE_OPTIONS", "model": "contact", "notes": "Generic Tier 1-4 options; the dollar amount lives in the package_tier_*_price custom values, NOT in the option label (avoids the Lux-specific hardcoding)." },
44
- { "ref": "field.intake_height", "name": "Intake - Height", "dataType": "TEXT", "model": "contact" },
45
- { "ref": "field.intake_weight", "name": "Intake - Weight", "dataType": "TEXT", "model": "contact" },
46
- { "ref": "field.intake_sex", "name": "Intake - Sex", "dataType": "SINGLE_OPTIONS", "model": "contact" },
47
- { "ref": "field.intake_physician_care", "name": "Intake - Under Physician Care", "dataType": "CHECKBOX", "model": "contact" },
48
- { "ref": "field.deposit_paid", "name": "Deposit Paid", "dataType": "CHECKBOX", "model": "contact" },
49
- { "ref": "field.balance_due", "name": "Balance Due At Appointment", "dataType": "MONETORY", "model": "contact" },
50
- { "ref": "field.lead_source", "name": "Lead Source", "dataType": "SINGLE_OPTIONS", "model": "contact" },
51
- { "ref": "field.ltv", "name": "Estimated Lifetime Value", "dataType": "MONETORY", "model": "contact" }
52
- ],
53
- "_fieldNote": "The full launch intake captures ~49 fields (the CLL onboarding form nI5dPig1RIJvd6vQNcEf + patient intake/consent). The skill ingests the operator's onboarding answers and creates the subset the workflows actually use; the consent/health fields above are representative. The full field list is in the CLL onboarding spec.",
54
- "tags": [
55
- { "ref": "tag.dbr_line_1", "name": "dbr-line-1" },
56
- { "ref": "tag.dbr_line_2", "name": "dbr-line-2" },
57
- { "ref": "tag.dbr_line_3", "name": "dbr-line-3" },
58
- { "ref": "tag.dbr_line_4", "name": "dbr-line-4" },
59
- { "ref": "tag.dbr_import", "name": "dbr-import" },
60
- { "ref": "tag.active_in_dbr", "name": "active-in-dbr" },
61
- { "ref": "tag.interested_in_dbr", "name": "interested-in-dbr" },
62
- { "ref": "tag.completed_dbr", "name": "completed-dbr" },
63
- { "ref": "tag.completed_dbr_appt", "name": "completed-dbr-appointment" },
64
- { "ref": "tag.dbr_waitlist", "name": "dbr-waitlist" },
65
- { "ref": "tag.event_sold_out", "name": "event-sold-out" },
66
- { "ref": "tag.link_sent_no_book", "name": "link-sent-no-book" },
67
- { "ref": "tag.balance_paid", "name": "balance-paid" },
68
- { "ref": "tag.intake_form_submitted", "name": "intake-form-submitted" },
69
- { "ref": "tag.package_purchased", "name": "package-purchased" },
70
- { "ref": "tag.patient_no_show", "name": "patient-no-show" },
71
- { "ref": "tag.patient_rescheduled", "name": "patient-rescheduled" }
72
- ],
73
- "customValues": [
74
- { "ref": "cv.business_name", "name": "business_name", "value": "{{business.name}}" },
75
- { "ref": "cv.clinic_name", "name": "clinic_name", "value": "{{business.name}}" },
76
- { "ref": "cv.clinic_director_name", "name": "clinic_director_name", "value": "" },
77
- { "ref": "cv.clinic_phone", "name": "clinic_phone", "value": "" },
78
- { "ref": "cv.clinic_address", "name": "clinic_address", "value": "{{business.location}}" },
79
- { "ref": "cv.event_start_date", "name": "event_start_date", "value": "" },
80
- { "ref": "cv.event_end_date", "name": "event_end_date", "value": "" },
81
- { "ref": "cv.event_capacity", "name": "event_capacity", "value": "" },
82
- { "ref": "cv.spots_left", "name": "spots_left", "value": "", "notes": "= event_capacity at launch" },
83
- { "ref": "cv.deposit_amount", "name": "deposit_amount", "value": "" },
84
- { "ref": "cv.trial_price", "name": "trial_price", "value": "" },
85
- { "ref": "cv.balance_amount", "name": "balance_amount", "value": "" },
86
- { "ref": "cv.package_tier_1_name", "name": "package_tier_1_name", "value": "" },
87
- { "ref": "cv.package_tier_1_price", "name": "package_tier_1_price", "value": "" },
88
- { "ref": "cv.package_tier_2_name", "name": "package_tier_2_name", "value": "" },
89
- { "ref": "cv.package_tier_2_price", "name": "package_tier_2_price", "value": "" },
90
- { "ref": "cv.package_tier_3_name", "name": "package_tier_3_name", "value": "" },
91
- { "ref": "cv.package_tier_3_price", "name": "package_tier_3_price", "value": "" },
92
- { "ref": "cv.booking_link", "name": "booking_link", "value": "", "filledBy": "handoff.calendar_config" },
93
- { "ref": "cv.deposit_link", "name": "deposit_link", "value": "", "filledBy": "handoff.stripe_products" },
94
- { "ref": "cv.balance_payment_link", "name": "balance_payment_link", "value": "", "filledBy": "handoff.stripe_products" },
95
- { "ref": "cv.package_tier_1_link", "name": "package_tier_1_link", "value": "", "filledBy": "handoff.stripe_products" },
96
- { "ref": "cv.package_tier_2_link", "name": "package_tier_2_link", "value": "", "filledBy": "handoff.stripe_products" },
97
- { "ref": "cv.package_tier_3_link", "name": "package_tier_3_link", "value": "", "filledBy": "handoff.stripe_products" },
98
- { "ref": "cv.intake_form_url", "name": "intake_form_url", "value": "", "filledBy": "form.intake" }
99
- ],
100
- "calendars": [
101
- {
102
- "ref": "calendar.event",
103
- "name": "{{business.name}} Launch Event || Launch Event",
104
- "calendarType": "event",
105
- "_slotDuration": "Carry the appointment length from the brief (brief.calendars[].durationMinutes, or the words in the calendar answer: \"15 minutes\" \u2192 15) onto slotDuration (minutes, slotDurationUnit \"mins\"). This preset value applies ONLY when the brief gives no length; without any slotDuration GoHighLevel builds 30-minute slots (finding 25).",
106
- "openHours": [{ "daysOfTheWeek": [1, 2, 3, 4, 5], "hours": [{ "openHour": 9, "openMinute": 0, "closeHour": 18, "closeMinute": 0 }] }],
107
- "availabilityType": 0,
108
- "slotDuration": 30,
109
- "requiresStaff": true,
110
- "notes": "Event-type calendar scoped to the event window (event_start_date..event_end_date) with a deposit (deposit_amount, e.g. $19). Dates/hours/deposit set at handoff.calendar_config."
111
- }
112
- ],
113
- "forms": [
114
- {
115
- "ref": "form.intake",
116
- "name": "{{business.name}} Patient Intake & Consent",
117
- "fields": [
118
- { "type": "standard", "key": "first_name", "required": true },
119
- { "type": "standard", "key": "last_name", "required": true },
120
- { "type": "standard", "key": "email", "required": true },
121
- { "type": "standard", "key": "phone", "required": false },
122
- { "type": "custom", "fieldRef": "field.intake_height", "required": false },
123
- { "type": "custom", "fieldRef": "field.intake_weight", "required": false },
124
- { "type": "custom", "fieldRef": "field.intake_sex", "required": false },
125
- { "type": "custom", "fieldRef": "field.intake_physician_care", "required": false }
126
- ],
127
- "notes": "Phone is NOT required and carries no asterisk (A2P opt-in rule). Full intake mirrors the CLL onboarding/consent field set."
128
- }
129
- ],
130
- "funnels": [],
131
- "_funnelNote": "The launch preset does NOT build a GHL funnel. The opt-in / compliance presence is the dedicated A2P compliance SITE (6-page template: index/about/services/contact/privacy/terms), deployed at handoff.a2p_site — its consent HTML is what A2P approval depends on, and it must be a real hosted site, not a GHL preview.",
132
- "emails": [
133
- { "ref": "email.deposit_confirm", "name": "Deposit Confirmation", "subject": "You're booked — deposit received", "copyDirection": "Confirm deposit + appointment date/time + location from custom values; what to bring; reassurance.", "mergeTags": ["contact.first_name"] },
134
- { "ref": "email.post_event_non_buyer", "name": "Post-Event Follow-up (non-buyer)", "subject": "Following up from your visit", "copyDirection": "72-hour close for non-buyers; restate the package value; CTA to a tier link; no health/income claims.", "mergeTags": ["contact.first_name"] }
135
- ],
136
- "sms": [
137
- { "ref": "sms.dbr_line_1", "name": "Main DBR SMS - Line 1", "copyDirection": "Reactivation opener to an opted-in list. Clinic name (not legal entity); one line; offer the booking step; include opt-out (Reply STOP). A2P-safe wording — NO weight-loss / health-claim / income language.", "mergeTags": ["contact.first_name"] },
138
- { "ref": "sms.booking_link", "name": "Send Booking Link", "copyDirection": "Send the booking link (cv.booking_link) on interest/keyword; one line; opt-out.", "mergeTags": ["contact.first_name"] },
139
- { "ref": "sms.balance_collect", "name": "Balance Collection", "copyDirection": "Request balance via balance_payment_link before/at appointment; opt-out.", "mergeTags": ["contact.first_name"] },
140
- { "ref": "sms.sold_out_waitlist", "name": "Sold-Out Waitlist", "copyDirection": "When event-sold-out: offer waitlist (Reply WAIT); set expectation; opt-out.", "mergeTags": ["contact.first_name"] }
141
- ],
142
- "workflows": [
143
- {
144
- "ref": "workflow.dbr_line_1",
145
- "name": "Main DBR SMS - Line 1",
146
- "trigger": { "type": "contact_tag", "tagRef": "tag.dbr_line_1" },
147
- "stopOnResponse": true,
148
- "sendWindow": "8am-10pm local",
149
- "actions": [
150
- { "type": "add_contact_tag", "tagRef": "tag.active_in_dbr" },
151
- { "type": "create_opportunity", "pipelineRef": "pipeline.launch", "stageRef": "stage.active_dbr", "name": "{{contact.name}} - DBR" },
152
- { "type": "send_sms", "smsRef": "sms.dbr_line_1" }
153
- ]
154
- },
155
- {
156
- "ref": "workflow.auto_tag_reply",
157
- "name": "Auto-Tag DBR Reply",
158
- "trigger": { "type": "customer_reply" },
159
- "stopOnResponse": false,
160
- "actions": [
161
- { "type": "add_contact_tag", "tagRef": "tag.interested_in_dbr" },
162
- { "type": "internal_notification", "to": "{{operator.userId}}", "title": "DBR reply", "body": "{{contact.first_name}} replied to the launch campaign" }
163
- ]
164
- },
165
- {
166
- "ref": "workflow.send_booking_link",
167
- "name": "Reply BOOK - Send Booking Link",
168
- "trigger": { "type": "inbound_keyword", "keyword": "BOOK" },
169
- "stopOnResponse": false,
170
- "actions": [
171
- { "type": "add_contact_tag", "tagRef": "tag.link_sent_no_book" },
172
- { "type": "send_sms", "smsRef": "sms.booking_link" }
173
- ]
174
- },
175
- {
176
- "ref": "workflow.deposit_paid",
177
- "name": "Deposit Paid - Confirm Appointment",
178
- "trigger": { "type": "appointment", "appointmentStatus": "confirmed", "calendarRef": "calendar.event" },
179
- "stopOnResponse": false,
180
- "actions": [
181
- { "type": "add_contact_tag", "tagRef": "tag.balance_paid" },
182
- { "type": "send_email", "emailRef": "email.deposit_confirm" },
183
- { "type": "internal_notification", "to": "{{operator.userId}}", "title": "Deposit paid", "body": "{{contact.first_name}} paid deposit + booked" }
184
- ]
185
- },
186
- {
187
- "ref": "workflow.no_show",
188
- "name": "Appointment No-Show - Alert + Status",
189
- "trigger": { "type": "appointment", "appointmentStatus": "noshow", "calendarRef": "calendar.event" },
190
- "stopOnResponse": false,
191
- "actions": [
192
- { "type": "add_contact_tag", "tagRef": "tag.patient_no_show" },
193
- { "type": "internal_notification", "to": "{{operator.userId}}", "title": "No-show", "body": "{{contact.first_name}} did not show" }
194
- ]
195
- },
196
- {
197
- "ref": "workflow.balance_collection",
198
- "name": "Balance Collection - Auto-Send",
199
- "trigger": { "type": "appointment", "appointmentStatus": "confirmed", "calendarRef": "calendar.event" },
200
- "stopOnResponse": false,
201
- "actions": [
202
- { "type": "wait", "value": 1, "unit": "days" },
203
- { "type": "send_sms", "smsRef": "sms.balance_collect" }
204
- ]
205
- },
206
- {
207
- "ref": "workflow.package_sold",
208
- "name": "Package Sold - Confirmation",
209
- "trigger": { "type": "contact_tag", "tagRef": "tag.package_purchased" },
210
- "stopOnResponse": false,
211
- "actions": [
212
- { "type": "internal_notification", "to": "{{operator.userId}}", "title": "Package sold", "body": "{{contact.first_name}} purchased a package" }
213
- ]
214
- },
215
- {
216
- "ref": "workflow.post_event_followup",
217
- "name": "Post-Event Follow-up (Non-Buyers, 72hr)",
218
- "trigger": { "type": "contact_tag", "tagRef": "tag.completed_dbr_appt" },
219
- "stopOnResponse": true,
220
- "actions": [
221
- { "type": "send_email", "emailRef": "email.post_event_non_buyer" }
222
- ]
223
- },
224
- {
225
- "ref": "workflow.sold_out_waitlist",
226
- "name": "Sold Out - DBR Waitlist",
227
- "trigger": { "type": "contact_tag", "tagRef": "tag.event_sold_out" },
228
- "stopOnResponse": false,
229
- "actions": [
230
- { "type": "add_contact_tag", "tagRef": "tag.dbr_waitlist" },
231
- { "type": "send_sms", "smsRef": "sms.sold_out_waitlist" }
232
- ]
233
- },
234
- {
235
- "ref": "workflow.reply_stop_dnd",
236
- "name": "Replied STOP - DND",
237
- "trigger": { "type": "inbound_keyword", "keyword": "STOP" },
238
- "stopOnResponse": false,
239
- "actions": [
240
- { "type": "internal_notification", "to": "{{operator.userId}}", "title": "Opt-out", "body": "{{contact.first_name}} replied STOP" }
241
- ]
242
- }
243
- ],
244
- "handoffs": [
245
- {
246
- "ref": "handoff.add_staff",
247
- "owner": "OPERATOR-UI",
248
- "title": "Add staff users (you, your coordinator, the client)",
249
- "trigger": "always",
250
- "instruction": "Add the event team as users in Settings > My Staff. The event calendar needs assignable staff and GHL Command cannot add users.",
251
- "produces": null,
252
- "successCheck": "Staff users present",
253
- "blocks": ["calendar.event"]
254
- },
255
- {
256
- "ref": "handoff.stripe_products",
257
- "owner": "OPERATOR-EXT",
258
- "title": "Connect Stripe + create products and payment links",
259
- "trigger": "always",
260
- "instruction": "Connect Stripe, create the deposit, balance, and package tier products, and generate payment links. Paste the links back so they fill the deposit_link / balance_payment_link / package_tier_*_link custom values.",
261
- "produces": "cv.deposit_link, cv.balance_payment_link, cv.package_tier_1_link, cv.package_tier_2_link, cv.package_tier_3_link",
262
- "successCheck": "Products exist; each payment link resolves",
263
- "blocks": ["cv.deposit_link", "cv.balance_payment_link", "cv.package_tier_1_link", "cv.package_tier_2_link", "cv.package_tier_3_link"]
264
- },
265
- {
266
- "ref": "handoff.calendar_config",
267
- "owner": "OPERATOR-UI",
268
- "title": "Configure the event calendar (dates, hours, deposit) + connect it",
269
- "trigger": "always",
270
- "instruction": "Set the event window (event_start_date..event_end_date), hours, and the deposit amount on the event calendar, and authorize your Google/Outlook connection. Copy the booking link back to fill booking_link.",
271
- "produces": "cv.booking_link",
272
- "successCheck": "Calendar shows the event dates + connected account; booking link resolves",
273
- "blocks": ["cv.booking_link"]
274
- },
275
- {
276
- "ref": "handoff.phone_numbers",
277
- "owner": "OPERATOR-UI",
278
- "title": "Purchase phone numbers (1 per ~500 contacts, max 4)",
279
- "trigger": "always",
280
- "instruction": "Buy SMS-capable numbers sized to your list (roughly one per 500 contacts, up to four). Required before the DBR SMS lines can run.",
281
- "produces": null,
282
- "successCheck": "Numbers present and SMS-capable",
283
- "blocks": ["workflow.dbr_line_1"]
284
- },
285
- {
286
- "ref": "handoff.a2p_site",
287
- "owner": "OPERATOR-EXT",
288
- "title": "Build + deploy your A2P compliance website",
289
- "trigger": "always",
290
- "instruction": "Deploy the 6-page compliance site (index/about/services/contact/privacy/terms) from the provided template, filling your real business details. The consent HTML must be exact (two unchecked checkboxes, brand name in <strong> matching the Trust Center, no inline styles, clean URLs, privacy + terms with the full SMS clause set). It must be a real hosted site, not a GHL preview. A2P approval depends on this.",
291
- "produces": null,
292
- "successCheck": "Live site returns 200; consent + privacy + terms pass the 8/8 + 7-item checks",
293
- "blocks": ["handoff.a2p_brand_campaign"]
294
- },
295
- {
296
- "ref": "handoff.a2p_brand_campaign",
297
- "owner": "OPERATOR-EXT",
298
- "title": "Submit A2P brand + campaign",
299
- "trigger": "always",
300
- "instruction": "Submit the A2P brand (exact IRS legal name, EIN, address) and campaign (use-case description, two sample messages, opt-in description) in the Trust Center. Avoid the forbidden-words list (weight loss, revenue/income claims, 'operated by', reactivation/lead-gen language). Save the full submission before sending. 7-14 day review.",
301
- "produces": null,
302
- "successCheck": "Brand + campaign status submitted/approved",
303
- "blocks": ["workflow.dbr_line_1", "workflow.send_booking_link", "workflow.balance_collection", "workflow.sold_out_waitlist", "sms.dbr_line_1", "sms.booking_link", "sms.balance_collect", "sms.sold_out_waitlist"],
304
- "_blocksNote": "Lists the SMS-bearing WORKFLOW refs (so they stay DRAFT until A2P is met) plus the sms.* asset refs — only an explicit workflow.* ref gates a workflow's publish state."
305
- },
306
- {
307
- "ref": "handoff.conv_ai",
308
- "owner": "OPERATOR-UI",
309
- "title": "Activate Conversation AI (normal + sold-out prompts)",
310
- "trigger": "always",
311
- "instruction": "Create the Conversation AI bot, paste the normal-mode and sold-out-mode prompts, and wire the sold-out swap to the event-sold-out tag.",
312
- "produces": null,
313
- "successCheck": "Bot active; sold-out prompt swap fires on event-sold-out",
314
- "blocks": []
315
- },
316
- {
317
- "ref": "handoff.sms_warming",
318
- "owner": "OPERATOR-EXT",
319
- "title": "SMS warming ramp (4 weeks, parallel with A2P)",
320
- "trigger": "always",
321
- "instruction": "Import the number pool and run the 4-week warming ramp so deliverability holds at launch volume. Runs in parallel with the A2P review window.",
322
- "produces": null,
323
- "successCheck": "Warming on track; deliverability healthy",
324
- "blocks": []
325
- },
326
- {
327
- "ref": "handoff.list_import",
328
- "owner": "OPERATOR-EXT",
329
- "title": "Receive + clean + split the contact list",
330
- "trigger": "always",
331
- "instruction": "Provide the contact list. It gets deduped, cleaned, and split per line (dbr-line-1..4). Test a batch of 5 before throttled mass enroll.",
332
- "produces": null,
333
- "successCheck": "Lists ready; test batch passes before mass enroll",
334
- "blocks": ["workflow.dbr_line_1"]
335
- }
336
- ]
337
- }
338
- }