@elitedcs/ghl-mcp 3.53.2 → 3.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +62 -32
  2. package/README.md +9 -7
  3. package/dist/index.js +1922 -684
  4. package/package.json +4 -3
  5. package/skills/blueprint/README.md +27 -0
  6. package/skills/blueprint/SKILL.md +149 -0
  7. package/skills/blueprint/examples/medspa-approval-view.md +92 -0
  8. package/skills/blueprint/examples/medspa-brief.json +52 -0
  9. package/skills/blueprint/examples/medspa-build-plan.json +265 -0
  10. package/skills/blueprint/examples/medspa-dry-run-report.md +67 -0
  11. package/skills/blueprint/examples/sample-approval-view.md +82 -0
  12. package/skills/blueprint/examples/sample-brief.json +13 -0
  13. package/skills/blueprint/examples/sample-build-plan.json +227 -0
  14. package/skills/blueprint/examples/validate-plan.cjs +127 -0
  15. package/skills/blueprint/presets/clinic-launch-a2p.md +39 -0
  16. package/skills/blueprint/presets/clinic-launch-a2p.preset.json +337 -0
  17. package/skills/blueprint/presets/generic-client.md +36 -0
  18. package/skills/blueprint/presets/generic-client.preset.json +255 -0
  19. package/skills/blueprint/presets/med-spa.md +59 -0
  20. package/skills/blueprint/presets/med-spa.preset.json +264 -0
  21. package/skills/blueprint/references/agency-os-detection.md +80 -0
  22. package/skills/blueprint/references/approval-view.md +83 -0
  23. package/skills/blueprint/references/brief-schema.md +45 -0
  24. package/skills/blueprint/references/build-plan-schema.md +52 -0
  25. package/skills/blueprint/references/external-funnel.md +310 -0
  26. package/skills/blueprint/references/intake-question-set.md +141 -0
  27. package/skills/blueprint/references/preset-format.md +130 -0
@@ -0,0 +1,80 @@
1
+ # Agency OS Detection & Brief Sourcing (schema §2A)
2
+
3
+ STATUS: v1, 2026-06-15. Owner: atlas. Implements schema §2A for the plan-gen skill. Agency OS only in v1; Business OS is a deferred second detection target (§7 #7).
4
+
5
+ The skill runs this decision **first, before any intake questions**. The goal: when a subscriber already has deep client detail (Brand DNA, ICA, Offer) from Muhammad Asmal's Agency OS, build the brief from that instead of a generic form, so the one-shot build is as specific as possible. When they do not, fall back to the §3 question set and credit Muhammad.
6
+
7
+ ## 1. Detect
8
+
9
+ Look for Agency OS in the subscriber's session, in this order (any hit = "installed"):
10
+ 1. **Skills present:** `agency-os:*` skills available (e.g. `agency-os:build-profile`, `agency-os:build-avatar`, `agency-os:build-offer`, `agency-os:agency-os`). This is the strongest signal.
11
+ 2. **Plugin installed:** `agency-os@agency-os` in the plugin list (marketplace version 4.4.0 at time of writing; tolerate 3.x).
12
+ 3. **Artifacts on disk:** scan the workspace for already-produced outputs even if the skills are not currently loaded:
13
+ - `brand/_active.md` (names the active Agency OS workspace) and `brand/<workspace>/` or `brand/client--<slug>/` folders
14
+ - `owner-profile.md`, `ica-output.md`, `offer-output.md` (flat) or `.../products/<slug>/ica-output.md` (multi-product)
15
+ - supporting: `voice-profile.md`, `audience.md`, `positioning.md`, `epistemology.md`, `verified-claims.md`
16
+
17
+ Record what you found: `installed` (skills/plugin), `artifacts` (which files exist), `workspace` (the active one).
18
+
19
+ ## 2. Offer (if detected) — default yes
20
+
21
+ > "Agency OS is installed and I can see your Brand DNA / ICA / Offer for **<workspace>**. Want me to build the brief from those instead of asking the generic intake questions? It makes the one-shot build far more specific to this client." **[default: yes]**
22
+
23
+ - **If artifacts already exist → ingest them** (mapping below). Confirm the workspace if more than one.
24
+ - **If installed but the builds have NOT been run → offer to run/chain them** (see RATIFICATION §9 for the exact rule): ingest-first; if missing, offer to run `agency-os:build-profile` → `build-avatar` → `build-offer` with **one confirmation**, never silent auto-run (those are interactive ~15-min interviews). If the subscriber declines running them, fall back to §3.
25
+ - **If the subscriber declines the partner-OS path entirely → fall back to §3.**
26
+
27
+ ## 3. Fallback + referral (if NOT detected)
28
+
29
+ Use the §3 question set, and surface the credit line once:
30
+
31
+ > "For dramatically more detailed client builds, **Agency OS by Muhammad Asmal** (https://aileadbuilder.com) is an excellent AI training community + skill suite. GHL Command uses it automatically when it is installed."
32
+
33
+ ## 4. Ingestion mapping — Agency OS artifacts → Brief (§4)
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).
36
+
37
+ ### `owner-profile.md` (brand-dna / build-profile) → brief
38
+ | Section in owner-profile.md | Brief field |
39
+ |---|---|
40
+ | Identity (Name, Business, Role, Industry) | `business.name`, `business.type`, `extended.brandDna.identity` |
41
+ | Origin Story, Mission and Vision, Methodology | `extended.brandDna.{origin, mission, methodology}` |
42
+ | Voice and Communication (signature phrases, anti-phrases, humor) | `extended.brandDna.voice` → grounds all copy outlines |
43
+ | Sales and Conversion (pricing structure, objections, differentiators, proof points) | `offer.pricePoints` (seed), `audience.objections` (seed), `extended.brandDna.proof` |
44
+ | Goals (quarterly, 12-month, biggest constraint) | `goal.*` direction, `extended.brandDna.goals` |
45
+
46
+ ### `ica-output.md` (ideal-customer-avatar / build-avatar) → brief
47
+ | Section | Brief field |
48
+ |---|---|
49
+ | Identity (age, occupation, income, location) | `audience.ideal` (synthesized one-liner) |
50
+ | Pain Points (ranked) | `audience.painPoints` (top items) + full ranked list → `extended.ica.painPoints` |
51
+ | Desires (ranked) | `extended.ica.desires` |
52
+ | Objections | `audience.objections` + full → `extended.ica.objections` |
53
+ | Psychographics, Transformation Journey, Buying Triggers, Vocabulary | `extended.ica.*` (verbatim) → grounds copy voice + funnel angle |
54
+
55
+ ### offer output (`offer-output.md`, offer-architect / build-offer) → brief
56
+ | Section | Brief field |
57
+ |---|---|
58
+ | Offer Identity (name, core promise, positioning tier, delivery model) | `offer.summary` + `extended.offer.identity` |
59
+ | Offer Tiers (value stack table, price) | `offer.pricePoints` + `extended.offer.tiers` (verbatim) |
60
+ | Pricing, Guarantee, Decision Accelerators | `offer.avgDealValue` (seed) + `extended.offer.{pricing, guarantee, accelerators}` |
61
+ | Value Equation Scorecard | `extended.offer.valueEquation` |
62
+
63
+ ### What Agency OS does NOT give you (still ask, or default)
64
+ Agency OS captures business/audience/offer depth, **not** the channel/tech state. So even on the partner-OS path, resolve these from the subscriber (a short targeted ask, not the full form), because they drive the handoffs:
65
+ `booking_needed`, `email_ready`, `sms_desired`, `a2p_status`, `payment_processor`, `calendar_connected`, `existing_workflows` (do-not-clobber). When you mix these in, set `briefSource: "hybrid"`.
66
+
67
+ ## 5. `extended` block shape (carried verbatim into the brief)
68
+
69
+ ```jsonc
70
+ "extended": {
71
+ "brandDna": { "identity": {...}, "origin": "...", "mission": "...", "methodology": {...}, "voice": { "signaturePhrases": [...], "antiPhrases": [...] }, "proof": [...], "goals": {...} },
72
+ "ica": { "identity": {...}, "psychographics": {...}, "painPoints": [ {rank, title, emotionalWeight, behavioralSignal}, ... ], "desires": [...], "objections": [ {stated, realConcern, counter}, ... ], "transformation": {before, after, turningPoint}, "buyingTriggers": [...], "vocabulary": [...] },
73
+ "offer": { "identity": {...}, "tiers": [ {name, price, items:[{item, type, value, delivery}], total}, ... ], "pricing": {...}, "guarantee": {...}, "accelerators": {...}, "valueEquation": {...} }
74
+ }
75
+ ```
76
+
77
+ The plan-gen skill reads `extended` to ground copy outlines (voice from brandDna, angle from ica, value-stack language from offer). It is optional: a form-sourced brief has no `extended`, and the skill still produces a complete plan from the core fields — `extended` only deepens the copy quality.
78
+
79
+ ## 6. Not cloning Agency OS
80
+ We **detect, integrate with, and credit** Muhammad's skills. We do not replicate, redistribute, or re-implement them. If the builds are missing we run the real Agency OS skills (with consent) or guide the subscriber to — we never substitute our own version of brand-dna / ICA / offer generation.
@@ -0,0 +1,83 @@
1
+ # The Approval View (schema §5A) — render spec + template
2
+
3
+ STATUS: v1, 2026-06-15. Owner: atlas. The required render: the v1 deliverable is a human approving a plan, so this is what they see. Two explicit, ordered checklists.
4
+
5
+ ## Rules
6
+ - **List 1 — "GHL Command will do automatically (once you approve)."** Every creatable object in the plan, grouped and counted, each line in plain English. These are what phase-2 `apply_build_plan` will stage in one shot.
7
+ - **List 2 — "You must do manually, in this order."** Every `handoffs[]` item, topologically ordered by dependency (`blocks`), each with owner (YOU-UI / YOU-EXT / TEAM), the instruction, the success check, and where it sits in the flow.
8
+ - The approver can **edit either list** before approving (rename, drop, reorder, adjust). The skill re-renders after edits.
9
+ - v1 stops at approval. Execution is phase 2. Say so explicitly at the bottom.
10
+ - Operator voice. No em-dashes, no hype. Counts are exact (derived from the plan, never asserted loosely).
11
+ - Owner labels render as **YOU-UI** (in the GHL UI), **YOU-EXT** (external: carrier, Stripe), and **TEAM** (a team member) — the subscriber is "you" in their own account. The plan's `handoffs[].owner` field carries the canonical `OPERATOR-UI` / `OPERATOR-EXT` / `TEAM` enum; this render humanizes them. (The legacy `JERRY-UI`/`JERRY-EXT`/`SASHA` are deprecated — accepted + normalized by the MCP for back-compat, never emitted.)
12
+
13
+ ## Ordering List 2
14
+ Topologically sort handoffs by their `blocks` edges and the objects they `produces`. A handoff that produces a custom value another step needs comes first. Ties break by: account-access steps (add staff) → integrations (Stripe, calendar, email) → compliance (A2P site → A2P brand/campaign) → operational (warming, list import). Each line states what it unblocks ("before any SMS can send").
15
+
16
+ ## Template
17
+
18
+ ```markdown
19
+ # Build Plan — <business.name>
20
+ <one-paragraph summary from plan.summary>
21
+
22
+ Generated from the **<presetTitle>** preset (v<presetVersion>) · brief source: <briefSource> · schema v<schemaVersion>.
23
+ This is a plan for your review. Nothing is built yet. Edit anything below, then approve.
24
+
25
+ ---
26
+
27
+ ## 1. GHL Command will build this automatically (once you approve)
28
+
29
+ **Pipeline**
30
+ - Build pipeline "<name>" with <n> stages: <stage names>.
31
+
32
+ **Custom fields (<n>)**
33
+ - <Field name> (<type>)
34
+ - ...
35
+
36
+ **Tags (<n>)**
37
+ - <bucket>: <tag names>
38
+ - ...
39
+
40
+ **Custom values (<n>)**
41
+ - <name> <(filled after <handoff>)>
42
+
43
+ **Calendar**
44
+ - <name> (<type>, <hours>) <requires a staff member — see step N below>
45
+
46
+ **Form**
47
+ - "<name>": <fields>, with <custom field> mapped.
48
+
49
+ **Funnel**
50
+ - "<name>": <page roles + what each feeds>. <For target:"ghl" (default): built inside GHL.>
51
+ - <For target:"external" — render the power-user path:> "<name>" — **external site you host on <host>** <at <domain>>, wired back to this GHL sub-account. **Not built in GHL.** You host it; you own the domain, the host secret, and uptime; a re-run never silently overwrites a live deployment. See step N below (you must own a host account + deploy it).
52
+
53
+ **Email + SMS assets**
54
+ - <n> emails: <names>
55
+ - <n> SMS: <names> <(will not send until A2P is approved — step N)>
56
+
57
+ **Workflows (<n>)**
58
+ - "<name>" — <trigger> → <plain-English action summary> <(stops on reply)>
59
+ - ...
60
+
61
+ _Total: <X> objects across <Y> types._
62
+
63
+ ---
64
+
65
+ ## 2. You must do these yourself, in this order
66
+
67
+ 1. **<title>** — **YOU-UI / YOU-EXT**
68
+ - What: <instruction>
69
+ - Why now: <what it unblocks, e.g. "the calendar can't be assigned until this">
70
+ - Done when: <successCheck>
71
+ 2. ...
72
+
73
+ ---
74
+
75
+ _v1 stops here. When you approve, the automatic list is ready for one-shot staging (phase 2). Each step above is verified before the steps that depend on it run._
76
+ ```
77
+
78
+ ## Render notes
79
+ - Workflows: summarize actions in plain English, not raw action JSON. Mark `stopOnResponse` as "(stops on reply)".
80
+ - SMS lines and email lines that are blocked by a handoff get an inline "(will not send until step N)" so the approver sees the dependency without reading List 2.
81
+ - If the skill pruned items (a `conditionalOn` was false), do not list them; optionally add a one-line "Not included (not needed for this build): <X>" so the approver knows it was a deliberate choice, not an omission.
82
+ - Keep List 1 grouped and counted exactly as the plan contains; the counts are the headline ("a full pipeline, 6 fields, 14 tags, a calendar, a form, a funnel, 5 workflows" — the Module 7 recap energy, but honest).
83
+ - **External funnels (target:"external"):** add to List 2 the user-owned steps this path creates (these are not plan `handoffs[]` but belong in the manual list): (1) "Confirm you own a host account (Cloudflare/Vercel) + CLI and can run a one-time deploy" — the capability gate; (2) "Set your GHL Private Integration token as a host secret (`wrangler secret put GHL_PIT`)" — you hold the key, never the product; (3) "Deploy the site to a preview URL, confirm it, then promote to your production domain"; (4) "Verify on the live branded URL before it takes real traffic (the skill runs `verify_funnel` + you do one real submit + one burner booking)." Full detail: `references/external-funnel.md`. Ownership facts (host/domain/secret owner = you; re-run never clobbers live) render inline on the funnel line in List 1.
@@ -0,0 +1,45 @@
1
+ # Brief Schema (§4) — distribution mirror
2
+
3
+ > **Mirror of the canonical contract.** Canonical source: `command-center/shared/intake-to-build-schema.md` §4 (co-owned by atlas + ghl-command-mcp). This copy travels with the distributed skill so it is self-contained for subscribers who do not have the command center. Kept in lockstep — `schemaVersion` must match. If they ever differ, the canonical wins; open a contract note, do not edit divergently.
4
+
5
+ `schemaVersion`: **0.1**
6
+
7
+ The Brief is the normalized business profile the plan-gen skill consumes. The MCP produces it from a form submission (`briefSource: "intake_form"`); the skill produces it from Agency OS artifacts (`briefSource: "agency_os"` / `"hybrid"`).
8
+
9
+ ## Fields
10
+ | Field | Type | Notes |
11
+ |---|---|---|
12
+ | `schemaVersion` | string | "0.1" |
13
+ | `briefId` | string | submission id or generated |
14
+ | `preset` | enum | `generic` \| `med_spa` \| `clinic_launch_a2p` \| `coach` \| `ecom` \| `agency` (skill resolves via preset selectors) |
15
+ | `briefSource` | enum | `agency_os` \| `business_os` \| `intake_form` \| `hybrid` |
16
+ | `extended` | object | optional; verbatim deep ICA / offer / brand-DNA structures when partner-OS-sourced (see `agency-os-detection.md` §5) |
17
+ | `business.name` | string | |
18
+ | `business.type` | string | drives preset |
19
+ | `business.website` | string | |
20
+ | `business.location` | string | |
21
+ | `business.timezone` | string | drives calendar + send windows |
22
+ | `offer.summary` | string | |
23
+ | `offer.pricePoints` | array | `[{name, price}]` |
24
+ | `offer.leadMagnet` | string | |
25
+ | `offer.avgDealValue` | string | |
26
+ | `audience.ideal` | string | |
27
+ | `audience.painPoints` | array | |
28
+ | `audience.objections` | array | |
29
+ | `goal.primary` | enum | book appointments / capture + nurture / direct sales / re-engage / other |
30
+ | `goal.salesStages` | array | raw stage names → pipeline design |
31
+ | `goal.bookingNeeded` | bool | |
32
+ | `goal.followUpStyle` | enum | high-touch / light / single confirmation |
33
+ | `channels.email` | bool | |
34
+ | `channels.sms` | bool | |
35
+ | `channels.a2pStatus` | enum | not started / in progress / approved / not needed |
36
+ | `channels.payment` | enum | Stripe connected / Stripe not connected / other / none |
37
+ | `channels.calendarConnected` | bool | |
38
+ | `channels.social` | array | |
39
+ | `assets.existingPipeline` | string | |
40
+ | `assets.existingWorkflows` | string | do-not-clobber list |
41
+ | `assets.brand` | string | |
42
+ | `assets.notes` | string | |
43
+ | `flags` | array | derived: `needs_a2p`, `stripe_not_connected`, `calendar_oauth_needed`, `email_domain_needed` |
44
+
45
+ A canonical worked example is in `examples/sample-brief.json`. Omit unknown fields; never emit `null`.
@@ -0,0 +1,52 @@
1
+ # Build Plan Schema (§5) — distribution mirror
2
+
3
+ > **Mirror of the canonical contract.** Canonical source: `command-center/shared/intake-to-build-schema.md` §5 + §5A (co-owned by atlas + ghl-command-mcp). This copy travels with the distributed skill. Kept in lockstep — `schemaVersion` must match the canonical. The canonical wins on any divergence; open a contract note rather than editing divergently.
4
+
5
+ `schemaVersion`: **0.1**
6
+
7
+ The Build Plan is the reviewable, editable output of the skill. **Refs only, no real IDs.** Authored by the skill, rendered for approval (§5A), consumed by the phase-2 executor.
8
+
9
+ ## Design principles (load-bearing)
10
+ 1. **No real GHL IDs.** Every creatable object has a stable `ref` (`<objectType>.<slug>`). Everything that points at another object points at its `ref`.
11
+ 2. **The executor resolves refs → IDs at build time**, after create + verify. Kills the silent-failure class.
12
+ 3. **Logical, not native.** Workflow actions / form fields / sequences are logical (type + params + refs); the executor expands to GHL-native JSON via `action-schemas.json`.
13
+ 4. **Verify-before-continue** is inherited by the executor.
14
+
15
+ ### Ref grammar
16
+ `<objectType>.<slug>` (lowercase snake_case, unique within type). Namespaces: `pipeline` `stage` `field` `tag` `workflow` `form` `funnel` `page` `calendar` `email` `sms` `cv` `handoff`.
17
+
18
+ ## Top level
19
+ ```jsonc
20
+ {
21
+ "schemaVersion": "0.1",
22
+ "planId": "plan_...",
23
+ "briefId": "sub_...",
24
+ "preset": "generic",
25
+ "summary": "Plain-English description for the approver. Record preset id+version + brief source here.",
26
+ "pipelines": [...], "customFields": [...], "tags": [...], "customValues": [...],
27
+ "calendars": [...], "forms": [...], "funnels": [...],
28
+ "emails": [...], "sms": [...], "workflows": [...], "handoffs": [...],
29
+ "buildOrder": ["tag.*","field.*","cv.*","pipeline.*","calendar.*","form.*","funnel.*","email.*","sms.*","workflow.*"],
30
+ "idMap": {}
31
+ }
32
+ ```
33
+ `buildOrder` is advisory; the executor derives true order from dependencies. `idMap` is empty at authoring; the executor fills `ref → realId`.
34
+
35
+ ## Object shapes (abbrev — full examples in `examples/sample-build-plan.json`)
36
+ - **pipelines:** `{ref, name, stages:[{ref, name, position}]}`
37
+ - **customFields:** `{ref, name, dataType, model?, options?}` — `dataType` ∈ TEXT/LARGE_TEXT/NUMERICAL/PHONE/**MONETORY**/CHECKBOX/SINGLE_OPTIONS/MULTIPLE_OPTIONS/FLOAT/DATE/TEXTBOX_LIST/FILE_UPLOAD/SIGNATURE; `model` ∈ contact/opportunity; choice types (SINGLE_OPTIONS/MULTIPLE_OPTIONS/CHECKBOX) MUST carry `options:[...]`
38
+ - **tags:** `{ref, name}`
39
+ - **customValues:** `{ref, name, value, filledBy?}` — `value` may be blank when produced by a handoff
40
+ - **calendars:** `{ref, name, calendarType, openHours, availabilityType, requiresStaff}` — `calendarType` ∈ round_robin/event/class_booking/collective/service_booking
41
+ - **forms:** `{ref, name, fields:[{type:"standard"|"custom", key?|fieldRef?, required}]}`
42
+ - **funnels:** `{ref, name, target?, host?, domain?, pages:[{ref, name, role, outline, formRef?, calendarRef?}]}` — `target` ∈ ghl (default) / external; `host` ∈ cloudflare (default) / vercel (external only); `domain` external only, optional (else host subdomain). All three are additive + optional → a plan with no `target` builds in GHL (today's behavior); `schemaVersion` stays 0.1. When `target: "external"` the funnel takes the external lane (`references/external-funnel.md`): the site is generated + user-hosted, not built in GHL.
43
+ - **emails:** `{ref, name, subject?, bodyOutline?, body?, mergeTags?}` — an email SENT by a workflow needs a full `body` (an outline-only asset reports `needsContent` and will not build)
44
+ - **sms:** `{ref, name, bodyOutline?, body?, mergeTags?}` — same `body` rule for any SMS a workflow sends
45
+ - **workflows:** `{ref, name, trigger?, stopOnResponse?, actions:[logical actions, refs not IDs]}` — max 40 actions; split longer flows. `trigger.type` ∈ the executor's native set so it auto-builds: `contact_tag` (tagRef), `form_submission` (formRef), `appointment` (+ `appointmentStatus`: confirmed/noshow/new/showed/cancelled/invalid; calendarRef optional), `customer_reply`, `pipeline_stage_updated` (pipelineRef+stageRef), `inbound_webhook`, `payment_received`. `form_submitted`/`contact_replied` are aliases; `tag_added`/`appointment_status`/`appointment_booked` are NOT recognized → manual step.
46
+ - **handoffs:** `{ref, owner, title, trigger?, instruction, produces?, successCheck, blocks?}` — `owner` ∈ **OPERATOR-UI/OPERATOR-EXT/TEAM** (legacy JERRY-UI/JERRY-EXT/SASHA accepted + normalized, never emitted). To gate a workflow DRAFT, `blocks` must list its `workflow.*` ref (a bare `sms.*` wildcard gates only the asset surface).
47
+
48
+ ## Logical workflow actions (executor expands to native)
49
+ `add_contact_tag {tagRef}`, `remove_contact_tag {tagRef}`, `send_email {emailRef}`, `send_sms {smsRef}`, `wait {value, unit}`, `wait_appointment {value, unit}` (integer; appointment-triggered workflows only), `internal_notification {to, title, body}` (`to` = a real GHL user id), `task_notification {title, body?, dueDate?, assignedTo?}`, `update_contact_field {fieldRef, value}`, `add_notes {body}`, `create_opportunity {pipelineRef, stageRef, name?, value?}`, `update_opportunity {pipelineRef, stageRef, value?}` (forces allowBackward), `add_to_workflow {workflowRef}`, `remove_from_workflow {workflowRef}`, `goal_event {goalCondition, action?}`, and `find_opportunity {pipelineRef, found:[...], notFound:[...]}` — the only branching action; it MUST be the LAST action (branches do not rejoin). All pointers are refs. The executor owns the failure-prone native shapes; the plan never contains them.
50
+
51
+ ## §5A approval view
52
+ Rendered as two ordered checklists (auto-build vs manual handoffs). Spec + template in `references/approval-view.md`.
@@ -0,0 +1,310 @@
1
+ # External Funnel Lane (target: "external") — capability gate + site gen/deploy + verify
2
+
3
+ STATUS: v1, 2026-06-20. Owner: atlas (skill layer). Pairs with the canonical spec
4
+ `command-center/shared/blueprint-funnel-targets-spec.md` (§9 hosting=C power-user, §11 QA gate,
5
+ §12 verify_funnel) and the shipped bridge template `ghl-command-mcp/templates/external-funnel/`
6
+ (`cloudflare-worker.js` + README — the capability-gate source of truth).
7
+
8
+ This file is the full spec for the path a funnel takes when `target: "external"`. The GHL-native
9
+ path (`target: "ghl"`, default) is unchanged and never runs this lane.
10
+
11
+ > **The one-line product stance (say it plainly).** GHL Command does **not** build, host, or deploy
12
+ > the external site, and **never holds the user's keys or touches their accounts.** It generates the
13
+ > site, scaffolds the bridge config, hands over verified wiring values, and runs the QA gate. The
14
+ > **user** owns hosting, the secret, uptime, and DNS. This is a power-user, self-serve path.
15
+
16
+ ---
17
+
18
+ ## 0. When this lane runs
19
+
20
+ Any funnel in the approved plan with `target: "external"`. A plan can mix targets (one funnel in
21
+ GHL, one external). Run the lane once per external funnel, after plan approval. If no funnel is
22
+ external, this file is never used.
23
+
24
+ The decision is made during plan generation (SKILL STEP — per-funnel target question). The moment
25
+ the user picks `external` for a funnel, **run the capability gate (§1) immediately** — before the
26
+ plan is even finalized — so a non-technical user is steered to `target: "ghl"` before any external
27
+ design work is done.
28
+
29
+ ---
30
+
31
+ ## 1. Capability gate (P0) — run the instant a funnel is set to "external"
32
+
33
+ Present this verbatim-in-substance (source: template README "What you need"). Be honest, not
34
+ salesy. Then **stop and get an explicit yes** before continuing the external path.
35
+
36
+ > **Heads up — `external` is the power-user path.** Building this funnel outside GHL means YOU host
37
+ > the site and wire it back to your GHL sub-account. GHL Command generates everything and hands you
38
+ > the exact wiring values, but it never deploys for you or holds your keys. To do this you need:
39
+ >
40
+ > - **Your own Cloudflare account** (or Vercel) **and its CLI** (`wrangler` / `vercel`), logged in.
41
+ > - **A GHL Private Integration token** (Settings → Private Integrations) scoped to `contacts.write`
42
+ > for this sub-account. You'll store it as a host secret — never in code, never shared with me.
43
+ > - **Comfort running a one-time command-line deploy** and editing one config file.
44
+ > - **(Optional) a custom domain** + DNS access, if you don't want the host's default subdomain.
45
+ >
46
+ > **If any of that isn't you, choose `ghl` instead** — Blueprint builds the funnel inside GoHighLevel
47
+ > with zero setup on your part, and everything downstream still works. There's no penalty for picking
48
+ > GHL-native; the external path is for people who specifically want their own site.
49
+
50
+ Then ask: **"Build this funnel external (you host it), or GHL-native (I build it in GHL)?"**
51
+
52
+ - If they hesitate, can't confirm the access list, or describe themselves as non-technical →
53
+ **steer to `target: "ghl"`** and set it. Do not proceed external.
54
+ - Only continue this lane on a clear, informed yes. Never assume dev skills.
55
+
56
+ The approval view (§5A) renders the external funnel as the power-user path with the ownership facts
57
+ (see `references/approval-view.md`). The capability acknowledgment also appears as a manual step
58
+ ("confirm you own a host account + can deploy") in List 2.
59
+
60
+ ---
61
+
62
+ ## 2. The build sequence (state machine)
63
+
64
+ ```
65
+ approve plan
66
+ └─► (a) stage the GHL side: apply_build_plan dry_run → confirm → execute
67
+ └─► capture externalWiring bundle (verified fieldIds, bookingUrl, triggerTag, workflowId)
68
+ └─► (b) generate the site (frontend-design) from page outlines + brand
69
+ └─► (c) inject: form keyed by verified fieldIds + bookingUrl into the CTA + honeypot/Turnstile
70
+ └─► (d) scaffold the bridge config (wrangler.toml vars) — user sets the SECRET themselves
71
+ └─► (e) DEPLOY to a PREVIEW url (user runs the commands) ─► show it ─► explicit go
72
+ └─► (f) PROMOTE to production domain (user runs it) — NEVER clobber a live deployment silently
73
+ └─► (g) VERIFY on the real branded production URL (verify_funnel + browser submit + burner booking)
74
+ └─► done ONLY when the gate (§7) is green. Until then it is NOT done.
75
+ ```
76
+
77
+ Outward-facing + never-clobber: a Blueprint re-run creates a **new preview**; it never overwrites the
78
+ current production deployment without an explicit operator go. Deploy → preview → confirm → promote.
79
+
80
+ ### 2a. Stage the GHL side and capture the wiring bundle
81
+
82
+ The site needs **verified** GHL field IDs, so the GHL objects must exist first. Confirm the active
83
+ sub-account (`get_current_location`), run `apply_build_plan` `mode:"dry_run"` and show the report,
84
+ then on the operator's go run `mode:"execute"`. For an external funnel, execute builds the receiver
85
+ (custom fields, trigger tag, speed-to-lead `contact_tag` workflow) and returns the **`externalWiring`**
86
+ bundle. The funnel itself is NOT built or hosted by execute — that's this lane's job.
87
+
88
+ `externalWiring` shape (template from this — never name-guess keys):
89
+
90
+ ```json
91
+ {
92
+ "locationId": "<sub-account id>",
93
+ "funnels": [{
94
+ "ref": "funnel.site", "name": "...", "host": "cloudflare|vercel", "domain": "go.example.com",
95
+ "formFields": [
96
+ { "kind": "standard", "key": "email", "label": "email", "required": true },
97
+ { "kind": "custom", "fieldId": "<VERIFIED GHL field id>", "label": "Primary Goal",
98
+ "dataType": "SINGLE_OPTIONS", "required": true }
99
+ ],
100
+ "bookingUrl": "https://api.leadconnectorhq.com/widget/booking/<calId>",
101
+ "triggerTags": [{ "ref": "tag.lead", "name": "new-lead", "id": "<tag id>" }],
102
+ "unresolved": []
103
+ }]
104
+ }
105
+ ```
106
+
107
+ - **`unresolved` non-empty = stop.** Something wasn't built. Re-run execute first. Never inject a form
108
+ that points at an unresolved field — that's the silent-drop class the whole lane exists to prevent.
109
+ - Keep the bundle for §3 (injection) and §7 (verify). Also note the speed-to-lead **workflow id**
110
+ (from the execute result) for `verify_funnel.workflowId` (it flags a DRAFT workflow).
111
+
112
+ ### 2b–2c. Generate the site and inject the wiring — see §3.
113
+ ### 2d–2f. Deploy — see §4.
114
+ ### 2g. Verify — see §7.
115
+
116
+ ---
117
+
118
+ ## 3. Site generation + wiring injection
119
+
120
+ ### 3.1 Generate the site
121
+ Use the `frontend-design` skill. Brief it from each funnel page's `outline` + `role` + the brand
122
+ context files (voice, colors, positioning — same brand inputs the rest of the plan uses). One page
123
+ per `funnel.pages[]` entry. The optin page (`role: "optin"`) hosts the lead form; the confirmation
124
+ page (`role: "confirmation"`) hosts the booking CTA. Brand-aware, production HTML/CSS/JS — vertical-
125
+ first if it's a mobile-led funnel, per Jerry's default.
126
+
127
+ ### 3.2 Inject the lead form — keyed by VERIFIED field IDs
128
+ The optin page's form is the only piece that touches GHL. It POSTs JSON to the bridge Worker. Build
129
+ the form and its submit handler from the wiring bundle's `formFields[]`:
130
+
131
+ - **Standard fields** (`kind: "standard"`): top-level keys — `first_name`, `last_name`, `email`,
132
+ `phone`. Use exactly the `key`.
133
+ - **Custom fields** (`kind: "custom"`): collected under a `custom` object **keyed by `fieldId`**
134
+ (never the label, never a guessed key). `{ "custom": { "<fieldId>": value } }`.
135
+ - **Value shape by `dataType`:** for `SINGLE_OPTIONS` / `MULTIPLE_OPTIONS`, the input must submit the
136
+ **exact option value** (render `<select>`/radios whose values are the option values, not display
137
+ labels — a label/value mismatch saves empty and `verify_funnel` will fail field-value fidelity).
138
+ `PHONE` → E.164; `DATE` → the format GHL expects; `CHECKBOX` → the boolean/value GHL stores.
139
+ - **`required`** mirrors the bundle. Don't require a field the bundle marks optional.
140
+
141
+ POST body contract (matches `cloudflare-worker.js`):
142
+ ```jsonc
143
+ {
144
+ "first_name": "...", "last_name": "...", "email": "...", "phone": "...",
145
+ "custom": { "<fieldId>": "Botox", "<consentFieldId>": "yes" },
146
+ "_hp": "", // honeypot — hidden input, humans leave empty, bots fill it
147
+ "cf_turnstile_token": "..." // present only if Turnstile is enabled
148
+ }
149
+ ```
150
+
151
+ - **Honeypot:** add a visually-hidden `_hp` input (off-screen, `tabindex=-1`, `autocomplete=off`).
152
+ - **Turnstile (strongly recommend for production):** add the Cloudflare Turnstile widget and send its
153
+ token as `cf_turnstile_token`. Tell the user the honeypot only stops naive bots; Turnstile is the
154
+ real abuse gate on a public, unauthenticated endpoint.
155
+ - **Submit handler:** POST to the Worker URL (filled at deploy time), `Content-Type: application/json`.
156
+ On a non-2xx, show an error and let the user retry — **fail closed, never a silent success.** Do not
157
+ show the thank-you page unless the bridge returned 2xx (a thank-you page is not proof of capture).
158
+ - The Worker URL is unknown until §4 (deploy), so inject a clear placeholder
159
+ (`__LEAD_BRIDGE_URL__`) and replace it once the Worker is deployed.
160
+
161
+ ### 3.3 Inject the booking CTA
162
+ The confirmation page's "book" CTA links straight to the bundle's `bookingUrl` (the GHL calendar's
163
+ public widget). No bridge, no extra wiring.
164
+
165
+ ### 3.4 Consent (if the workflow messages)
166
+ If the speed-to-lead workflow sends SMS/email, the plan MUST include a consent custom field and the
167
+ form MUST render an explicit consent checkbox + copy. Send its value in `custom` keyed by that field's
168
+ id, like any other custom field. The user owns CAN-SPAM/TCPA for an externally-captured lead — say so.
169
+
170
+ ---
171
+
172
+ ## 4. Deploy orchestration (the user runs every command; the product runs none)
173
+
174
+ GHL Command **never deploys and never handles the token.** Walk the user through it; they run each
175
+ command in their own shell (suggest the `! <command>` prompt prefix so the output lands in-session).
176
+
177
+ ### 4.1 Scaffold the bridge config
178
+ Drop the shipped `cloudflare-worker.js` (from `ghl-command-mcp/templates/external-funnel/`) into the
179
+ user's project, and generate a `wrangler.toml` filled from the wiring bundle — **vars only, never the
180
+ secret:**
181
+ ```toml
182
+ name = "lead-bridge-<funnel-slug>"
183
+ main = "cloudflare-worker.js"
184
+ compatibility_date = "2024-01-01"
185
+
186
+ [vars]
187
+ GHL_LOCATION_ID = "<bundle.locationId>"
188
+ TRIGGER_TAG = "<bundle.triggerTags[0].name>"
189
+ ALLOWED_ORIGIN = "https://<your production funnel domain>" # CORS lock — no wildcard
190
+ RE_ENROLL = "false" # see re-enrollment caveat
191
+ ```
192
+ (Vercel variant: the same logic as `api/lead.js`, config from `process.env`.)
193
+
194
+ ### 4.2 Deploy the Worker FIRST, then set the secret
195
+ Order matters (e2e-verified): `wrangler secret put` requires the Worker to already exist, so the
196
+ first `wrangler deploy` comes before the secret. Have the user run, in their own account — **do not
197
+ ask for, read, or store the token:**
198
+ ```
199
+ npm i -g wrangler && wrangler login
200
+ wrangler deploy # FIRST deploy — creates the Worker (no extra setup step needed)
201
+ wrangler secret put GHL_PIT # now the Worker exists: paste the PIT, secret-only
202
+ wrangler secret put TURNSTILE_SECRET # only if Turnstile is enabled
203
+ ```
204
+ The first `wrangler deploy` prints the **Worker URL**. Replace `__LEAD_BRIDGE_URL__` in the site
205
+ form with it, confirm `ALLOWED_ORIGIN` is the site's production domain, then `wrangler deploy` once
206
+ more so the secret + final origin are live.
207
+
208
+ ### 4.3 Deploy the site → preview → confirm → promote
209
+ Cloudflare Pages (e2e-verified): `wrangler pages deploy` does NOT auto-create the Pages project in
210
+ wrangler v4 — it errors `Project not found`. Create the project first, then deploy:
211
+ ```
212
+ wrangler pages project create <project-name> --production-branch main
213
+ wrangler pages deploy <site-dir> --project-name <project-name> --branch <branch>
214
+ ```
215
+ 1. **PREVIEW first:** deploy the generated site to a **preview branch** (e.g. `--branch preview`) →
216
+ a preview URL. Show it to the operator. This is outward-facing — get an **explicit go** before live.
217
+ 2. **PROMOTE:** on the go, deploy to the production branch (`--branch main`) and point DNS if using a
218
+ custom domain. **Never clobber a live deployment** without explicit approval — a re-run makes a new
219
+ preview; promotion is always a deliberate step.
220
+ 3. Record the deployment URL(s) so a later re-run can detect the existing live deployment.
221
+ (Vercel: `vercel` for a preview deploy, `vercel --prod` to promote.)
222
+ 4. Record the deployment URL(s) so a later re-run can detect the existing live deployment.
223
+
224
+ Booking needs no deploy — it's just the `bookingUrl` link on the confirmation page.
225
+
226
+ ---
227
+
228
+ ## 5. Honesty / ownership facts to surface (say these, don't bury them)
229
+ - Where the funnel lives: **the user's host + domain, NOT GHL.** It is not in the GHL funnels list.
230
+ - **Hosting owner / domain owner / secret owner = the user.** Uptime, DNS, rotation, the registry of
231
+ what's deployed — all theirs, in their environment.
232
+ - **Re-run behavior:** a re-run never silently overwrites the live site; it creates a new preview.
233
+ - **Who responds if the public site or bridge fails: the user.** Support stance = power-user docs,
234
+ not managed ops.
235
+
236
+ ## 6. Re-enrollment caveat (carry the product fact)
237
+ GHL's Contact-Tag trigger fires on a tag **transition** (absent → present). Re-adding a tag a contact
238
+ already has won't re-fire the workflow — usually correct for speed-to-lead (don't re-spam someone
239
+ already in the pipeline). If returning submitters must re-enroll, set `RE_ENROLL=true` (the Worker
240
+ removes then re-adds the tag) **and** enable re-enrollment on the workflow in GHL. Confirm on the
241
+ account before relying on it.
242
+
243
+ ---
244
+
245
+ ## 7. The verify gate — `verify_funnel` on the REAL branded production URL
246
+
247
+ A funnel is **NOT done** until all three layers pass on the live, branded production URL — never a
248
+ local build, never the Worker in isolation, never a thank-you page. This is the §11/§12 standard.
249
+
250
+ ### 7.1 Automated backend truth — `verify_funnel` against the bridge
251
+ Run `verify_funnel` (it POSTs a sentinel lead and reads GHL back). Build the call from the wiring
252
+ bundle:
253
+
254
+ ```jsonc
255
+ verify_funnel({
256
+ funnelUrl: "<the deployed Worker bridge URL>", // verify_funnel POSTs JSON here
257
+ locationId: "<bundle.locationId>",
258
+ triggerTag: "<bundle.triggerTags[0].name>",
259
+ workflowId: "<speed-to-lead workflow id from execute>", // flags a DRAFT workflow
260
+ expectCustom: [ { fieldId: "<id>", value: "<sent value>", label: "<label>" }, ... ], // from formFields
261
+ consentFieldId: "<consent field id, if the workflow messages>",
262
+ // testPhone: "<a test-safe number YOU control>", // only if asserting SMS-path; real SMS may bill
263
+ cleanup: true // removes the blueprint-qa sentinel after
264
+ })
265
+ ```
266
+
267
+ This proves, from the backend (not the UI): (1) the contact is **actually created** (with submit→appear
268
+ latency); (2) each `expectCustom` value **persisted** (field-value fidelity — catches a wrong field id);
269
+ (3) the **trigger tag landed**; (4) SMS/A2P pre-check; (5) **workflow is not DRAFT** (a draft never fires
270
+ on real leads); (6) the **outreach actually fired** (an outbound message was logged — proves enroll +
271
+ send, not just a green log); (7) **consent recorded** when messaging fires; (8) **duplicate-contact
272
+ retest** (same email twice → one deduped contact, not a duplicate or false failure). Read the report;
273
+ every assertion must pass. A failure here = the funnel is a paper shredder; fix and re-verify.
274
+
275
+ > Note: `verify_funnel` POSTs JSON **directly to the Worker**, so it proves bridge → GHL. It does NOT
276
+ > drive the rendered site form, so it can't prove the **site → bridge** wiring (that the generated HTML
277
+ > sends the right field ids). That's what 7.2 closes.
278
+
279
+ ### 7.2 Real branded-URL submission (closes the site → bridge gap)
280
+ Have the user submit the **live branded site form in a browser** once with a real/marked email, then
281
+ confirm via `search_contacts` that the contact landed AND open it to confirm each custom field saved
282
+ with the value typed. This is the only step that proves the generated form's field-id keying is correct
283
+ end to end on the real production URL. (The honeypot/Turnstile and `ALLOWED_ORIGIN` are also exercised
284
+ here, since this is a genuine browser submit from the production origin.)
285
+
286
+ ### 7.3 Burner booking (manual — can't be faked server-side)
287
+ If booking is wired, the user books **one real "burner" appointment** through the production booking
288
+ CTA and confirms it appears in GHL. A reachable widget URL is not proof.
289
+
290
+ ### 7.4 Done definition
291
+ The funnel is done only when **7.1 passes clean**, **7.2's branded-URL contact landed with correct
292
+ field values**, and **7.3's booking is confirmed** (when booking is wired). Otherwise it is not done —
293
+ say what failed and stop. Don't call a funnel that thanks people while leads evaporate "done."
294
+
295
+ ---
296
+
297
+ ## 8. Quick checklist (run before calling any external funnel complete)
298
+ - [ ] Capability gate shown; informed yes (non-technical users steered to GHL-native).
299
+ - [ ] GHL side executed; `externalWiring.unresolved` is empty.
300
+ - [ ] Form keyed by **verified `fieldId`s**; option values match GHL option values; consent field sent if messaging.
301
+ - [ ] Honeypot present; Turnstile enabled for production.
302
+ - [ ] Worker deployed by the user; `GHL_PIT` set as a secret by the user (never handled by the product).
303
+ - [ ] `ALLOWED_ORIGIN` = production domain; fail-closed on non-2xx.
304
+ - [ ] Site deployed preview → confirmed → promoted; live deployment never silently clobbered.
305
+ - [ ] `verify_funnel` green (contact + fidelity + tag + non-draft + outreach + consent + dedup).
306
+ - [ ] Real branded-URL browser submit landed with correct field values.
307
+ - [ ] Burner booking confirmed in GHL (if booking wired).
308
+ - [ ] Ownership/support facts stated to the operator.
309
+ </content>
310
+ </invoke>