@elitedcs/ghl-mcp 3.48.2 → 3.49.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.
package/dist/index.js CHANGED
@@ -31,15 +31,16 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "@elitedcs/ghl-mcp",
34
- version: "3.48.2",
34
+ version: "3.49.0",
35
35
  mcpName: "io.github.drjerryrelth/ghl-command",
36
- description: "GoHighLevel MCP Server for Claude. 228 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.",
36
+ description: "GoHighLevel MCP Server for Claude. 229 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.",
37
37
  main: "dist/index.js",
38
38
  bin: {
39
39
  "ghl-mcp": "dist/index.js"
40
40
  },
41
41
  files: [
42
42
  "dist/index.js",
43
+ "dist/capture-helper.js",
43
44
  "templates/action-schemas.json",
44
45
  "templates/clinic-medspa.json",
45
46
  "templates/trigger-schemas.json",
@@ -49,7 +50,7 @@ var require_package = __commonJS({
49
50
  "CHANGELOG.md"
50
51
  ],
51
52
  scripts: {
52
- build: "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.js --packages=external",
53
+ 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",
53
54
  setup: "node setup-wizard.mjs",
54
55
  start: "node dist/index.js",
55
56
  dev: "tsc --watch",
@@ -92,6 +93,7 @@ var require_package = __commonJS({
92
93
  dependencies: {
93
94
  "@modelcontextprotocol/sdk": "^1.12.1",
94
95
  dotenv: "^16.5.0",
96
+ "playwright-core": "^1.61.1",
95
97
  zod: "^3.24.4"
96
98
  },
97
99
  devDependencies: {
@@ -108,8 +110,8 @@ var require_package = __commonJS({
108
110
 
109
111
  // src/index.ts
110
112
  var dotenv2 = __toESM(require("dotenv"));
111
- var path6 = __toESM(require("path"));
112
- var fs6 = __toESM(require("fs"));
113
+ var path7 = __toESM(require("path"));
114
+ var fs7 = __toESM(require("fs"));
113
115
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
114
116
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
115
117
 
@@ -173,8 +175,8 @@ var GHLClient = class {
173
175
  Version: version || GHL_API_VERSION
174
176
  };
175
177
  }
176
- buildUrl(path7, params) {
177
- const url = new URL(path7, GHL_BASE_URL);
178
+ buildUrl(path8, params) {
179
+ const url = new URL(path8, GHL_BASE_URL);
178
180
  if (params) {
179
181
  for (const [key, value] of Object.entries(params)) {
180
182
  if (value !== void 0 && value !== null) {
@@ -184,8 +186,8 @@ var GHLClient = class {
184
186
  }
185
187
  return url.toString();
186
188
  }
187
- async request(method, path7, options = {}, attempt = 0) {
188
- const url = this.buildUrl(path7, options.params);
189
+ async request(method, path8, options = {}, attempt = 0) {
190
+ const url = this.buildUrl(path8, options.params);
189
191
  const headers = this.buildHeaders(options.version);
190
192
  const fetchOptions = {
191
193
  method,
@@ -203,14 +205,14 @@ var GHLClient = class {
203
205
  } catch (error) {
204
206
  clearTimeout(timeout);
205
207
  if (error instanceof Error && error.name === "AbortError") {
206
- throw new Error(`Request timeout (30s): ${method} ${path7}`);
208
+ throw new Error(`Request timeout (30s): ${method} ${path8}`);
207
209
  }
208
210
  if (!options.noRetry && attempt < MAX_RETRIES) {
209
211
  const delay4 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
210
- process.stderr.write(`[ghl-mcp] Network error on ${method} ${path7}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
212
+ process.stderr.write(`[ghl-mcp] Network error on ${method} ${path8}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
211
213
  `);
212
214
  await new Promise((r) => setTimeout(r, delay4));
213
- return this.request(method, path7, options, attempt + 1);
215
+ return this.request(method, path8, options, attempt + 1);
214
216
  }
215
217
  throw error;
216
218
  } finally {
@@ -218,10 +220,10 @@ var GHLClient = class {
218
220
  }
219
221
  if (!options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
220
222
  const delay4 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
221
- process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path7}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
223
+ process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path8}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
222
224
  `);
223
225
  await new Promise((r) => setTimeout(r, delay4));
224
- return this.request(method, path7, options, attempt + 1);
226
+ return this.request(method, path8, options, attempt + 1);
225
227
  }
226
228
  if (!response.ok) {
227
229
  let errorBody = "";
@@ -230,7 +232,7 @@ var GHLClient = class {
230
232
  } catch {
231
233
  }
232
234
  throw new Error(
233
- `GHL API Error ${response.status} ${response.statusText}: ${method} ${path7}
235
+ `GHL API Error ${response.status} ${response.statusText}: ${method} ${path8}
234
236
  ${errorBody}`
235
237
  );
236
238
  }
@@ -242,20 +244,20 @@ ${errorBody}`
242
244
  return { message: text };
243
245
  }
244
246
  }
245
- async get(path7, options) {
246
- return this.request("GET", path7, options);
247
+ async get(path8, options) {
248
+ return this.request("GET", path8, options);
247
249
  }
248
- async post(path7, options) {
249
- return this.request("POST", path7, options);
250
+ async post(path8, options) {
251
+ return this.request("POST", path8, options);
250
252
  }
251
- async put(path7, options) {
252
- return this.request("PUT", path7, options);
253
+ async put(path8, options) {
254
+ return this.request("PUT", path8, options);
253
255
  }
254
- async patch(path7, options) {
255
- return this.request("PATCH", path7, options);
256
+ async patch(path8, options) {
257
+ return this.request("PATCH", path8, options);
256
258
  }
257
- async delete(path7, options) {
258
- return this.request("DELETE", path7, options);
259
+ async delete(path8, options) {
260
+ return this.request("DELETE", path8, options);
259
261
  }
260
262
  /**
261
263
  * Helper: resolves locationId from args or falls back to default
@@ -4584,16 +4586,16 @@ function registerEmailTools(server2, client) {
4584
4586
  function registerEmailBuilderInternalTools(server2, builderClient) {
4585
4587
  const client = builderClient;
4586
4588
  if (!client) return;
4587
- async function builderRequest(method, path7, body) {
4589
+ async function builderRequest(method, path8, body) {
4588
4590
  const headers = await client.buildHeaders();
4589
- const response = await fetch(`${EMAIL_BUILDER_BASE}${path7}`, {
4591
+ const response = await fetch(`${EMAIL_BUILDER_BASE}${path8}`, {
4590
4592
  method,
4591
4593
  headers,
4592
4594
  body: body ? JSON.stringify(body) : void 0
4593
4595
  });
4594
4596
  if (!response.ok) {
4595
4597
  const text2 = await response.text();
4596
- throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path7}
4598
+ throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path8}
4597
4599
  ${text2}`);
4598
4600
  }
4599
4601
  const text = await response.text();
@@ -5850,23 +5852,23 @@ var import_zod34 = require("zod");
5850
5852
  function registerFunnelBuilderTools(server2, builderClient) {
5851
5853
  const client = builderClient;
5852
5854
  if (!client) return;
5853
- async function internalGet(path7) {
5854
- return client.request("GET", path7);
5855
+ async function internalGet(path8) {
5856
+ return client.request("GET", path8);
5855
5857
  }
5856
- async function internalPost(path7, body) {
5857
- return client.request("POST", path7, body);
5858
+ async function internalPost(path8, body) {
5859
+ return client.request("POST", path8, body);
5858
5860
  }
5859
- async function internalPut(path7, body) {
5860
- return client.request("PUT", path7, body);
5861
+ async function internalPut(path8, body) {
5862
+ return client.request("PUT", path8, body);
5861
5863
  }
5862
- async function internalDelete(path7) {
5863
- return client.request("DELETE", path7);
5864
+ async function internalDelete(path8) {
5865
+ return client.request("DELETE", path8);
5864
5866
  }
5865
- async function funnelRequest(method, path7, body) {
5867
+ async function funnelRequest(method, path8, body) {
5866
5868
  const headers = await client.buildHeaders();
5867
5869
  headers.Origin = "https://app.gohighlevel.com";
5868
5870
  headers.Referer = "https://app.gohighlevel.com/";
5869
- const url = `https://backend.leadconnectorhq.com/funnels${path7}`;
5871
+ const url = `https://backend.leadconnectorhq.com/funnels${path8}`;
5870
5872
  const options = { method, headers };
5871
5873
  if (body && (method === "POST" || method === "PUT")) {
5872
5874
  options.body = JSON.stringify(body);
@@ -5874,7 +5876,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
5874
5876
  const response = await fetch(url, options);
5875
5877
  if (!response.ok) {
5876
5878
  const text2 = await response.text();
5877
- throw new Error(`Funnel API Error ${response.status}: ${method} ${path7}
5879
+ throw new Error(`Funnel API Error ${response.status}: ${method} ${path8}
5878
5880
  ${text2}`);
5879
5881
  }
5880
5882
  const text = await response.text();
@@ -6184,9 +6186,9 @@ function buildUpdateFormPath(formId, locationId2) {
6184
6186
  function buildUpdateFormBody(name, formData) {
6185
6187
  return { name, formData };
6186
6188
  }
6187
- async function formApiRequest(client, method, path7, body) {
6189
+ async function formApiRequest(client, method, path8, body) {
6188
6190
  const headers = await client.buildHeaders();
6189
- const url = `https://backend.leadconnectorhq.com/forms${path7}`;
6191
+ const url = `https://backend.leadconnectorhq.com/forms${path8}`;
6190
6192
  const options = { method, headers };
6191
6193
  if (body && (method === "POST" || method === "PUT")) {
6192
6194
  options.body = JSON.stringify(body);
@@ -6194,7 +6196,7 @@ async function formApiRequest(client, method, path7, body) {
6194
6196
  const response = await fetch(url, options);
6195
6197
  if (!response.ok) {
6196
6198
  const text2 = await response.text();
6197
- throw new Error(`Form API Error ${response.status}: ${method} ${path7}
6199
+ throw new Error(`Form API Error ${response.status}: ${method} ${path8}
6198
6200
  ${text2}`);
6199
6201
  }
6200
6202
  const text = await response.text();
@@ -6208,7 +6210,7 @@ ${text2}`);
6208
6210
  function registerFormBuilderTools(server2, builderClient, publicClient) {
6209
6211
  const client = builderClient;
6210
6212
  if (!client) return;
6211
- const formRequest = (method, path7, body) => formApiRequest(client, method, path7, body);
6213
+ const formRequest = (method, path8, body) => formApiRequest(client, method, path8, body);
6212
6214
  server2.tool(
6213
6215
  "get_form_full",
6214
6216
  "Get a form with full builder data: all fields (labels, types, IDs, validation), conditional logic, auto-responder config, email notification settings, styling, and version history. This is the internal API \u2014 it returns everything the form builder UI shows.",
@@ -6329,10 +6331,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
6329
6331
  },
6330
6332
  async ({ formId, limit, skip }) => {
6331
6333
  try {
6332
- let path7 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
6333
- if (formId) path7 += `&formId=${formId}`;
6334
- if (skip) path7 += `&skip=${skip}`;
6335
- const result = await formRequest("GET", path7);
6334
+ let path8 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
6335
+ if (formId) path8 += `&formId=${formId}`;
6336
+ if (skip) path8 += `&skip=${skip}`;
6337
+ const result = await formRequest("GET", path8);
6336
6338
  return {
6337
6339
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
6338
6340
  };
@@ -6725,9 +6727,9 @@ var import_zod37 = require("zod");
6725
6727
  function registerPipelineBuilderTools(server2, builderClient) {
6726
6728
  const client = builderClient;
6727
6729
  if (!client) return;
6728
- async function pipelineRequest(method, path7, body) {
6730
+ async function pipelineRequest(method, path8, body) {
6729
6731
  const headers = await client.buildHeaders();
6730
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path7}`;
6732
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path8}`;
6731
6733
  const options = { method, headers };
6732
6734
  if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
6733
6735
  options.body = JSON.stringify(body);
@@ -6735,7 +6737,7 @@ function registerPipelineBuilderTools(server2, builderClient) {
6735
6737
  const response = await fetch(url, options);
6736
6738
  if (!response.ok) {
6737
6739
  const text2 = await response.text();
6738
- throw new Error(`Pipeline API Error ${response.status}: ${method} ${path7}
6740
+ throw new Error(`Pipeline API Error ${response.status}: ${method} ${path8}
6739
6741
  ${text2}`);
6740
6742
  }
6741
6743
  const text = await response.text();
@@ -6877,12 +6879,12 @@ ${text2}`);
6877
6879
  }
6878
6880
 
6879
6881
  // src/tools/location-switcher.ts
6880
- var import_zod39 = require("zod");
6882
+ var import_zod40 = require("zod");
6881
6883
 
6882
6884
  // src/setup-tool.ts
6883
6885
  var os2 = __toESM(require("os"));
6884
6886
  var crypto2 = __toESM(require("crypto"));
6885
- var import_zod38 = require("zod");
6887
+ var import_zod39 = require("zod");
6886
6888
 
6887
6889
  // src/firebase-capture-script.ts
6888
6890
  var FIREBASE_CAPTURE_SCRIPT = `(async () => {
@@ -6969,6 +6971,147 @@ function parseFirebasePaste(input) {
6969
6971
  };
6970
6972
  }
6971
6973
 
6974
+ // src/interactive-capture.ts
6975
+ var fs4 = __toESM(require("fs"));
6976
+ var path4 = __toESM(require("path"));
6977
+ var import_child_process = require("child_process");
6978
+ var import_zod38 = require("zod");
6979
+ function captureStatePath() {
6980
+ return path4.join(appDataDir(), "interactive-capture.json");
6981
+ }
6982
+ var CaptureStateSchema = import_zod38.z.object({
6983
+ status: import_zod38.z.enum(["waiting_login", "captured", "timeout", "error"]),
6984
+ startedAt: import_zod38.z.string(),
6985
+ updatedAt: import_zod38.z.string(),
6986
+ // Present when status === "captured"
6987
+ ghl_firebase_api_key: import_zod38.z.string().optional(),
6988
+ ghl_user_id: import_zod38.z.string().optional(),
6989
+ ghl_firebase_refresh_token: import_zod38.z.string().optional(),
6990
+ account_email: import_zod38.z.string().optional(),
6991
+ // Present when status === "error"
6992
+ error: import_zod38.z.string().optional()
6993
+ });
6994
+ function readCaptureState() {
6995
+ try {
6996
+ const raw = fs4.readFileSync(captureStatePath(), "utf8");
6997
+ const parsed = CaptureStateSchema.safeParse(JSON.parse(raw));
6998
+ return parsed.success ? parsed.data : null;
6999
+ } catch {
7000
+ return null;
7001
+ }
7002
+ }
7003
+ function clearCaptureState() {
7004
+ try {
7005
+ fs4.unlinkSync(captureStatePath());
7006
+ } catch {
7007
+ }
7008
+ }
7009
+ function isStale(state, maxAgeMs) {
7010
+ const t = Date.parse(state.updatedAt);
7011
+ if (Number.isNaN(t)) return true;
7012
+ return Date.now() - t > maxAgeMs;
7013
+ }
7014
+ var EXTRACT_FIREBASE_PAGE_FN = `async () => {
7015
+ const pick = (rows) => {
7016
+ const candidates = rows.filter(r => typeof r?.fbase_key === 'string' && r.fbase_key.startsWith('firebase:authUser:AIza') && r?.value?.apiKey && r?.value?.uid && r?.value?.stsTokenManager?.refreshToken);
7017
+ if (!candidates.length) return null;
7018
+ const v = candidates[0].value;
7019
+ return {
7020
+ ghl_firebase_api_key: v.apiKey,
7021
+ ghl_user_id: v.uid,
7022
+ ghl_firebase_refresh_token: v.stsTokenManager.refreshToken,
7023
+ account_email: v.email || null,
7024
+ };
7025
+ };
7026
+ try {
7027
+ let poisoned = false;
7028
+ if (typeof indexedDB.databases === 'function') {
7029
+ const dbs = await indexedDB.databases();
7030
+ if (dbs.some(d => d.name === 'firebaseLocalStorageDb')) {
7031
+ const db = await new Promise((res, rej) => {
7032
+ const r = indexedDB.open('firebaseLocalStorageDb');
7033
+ r.onsuccess = () => res(r.result);
7034
+ r.onerror = () => rej(r.error);
7035
+ });
7036
+ if (db.objectStoreNames.contains('firebaseLocalStorage')) {
7037
+ const tx = db.transaction('firebaseLocalStorage', 'readonly');
7038
+ const store = tx.objectStore('firebaseLocalStorage');
7039
+ const rows = await new Promise((res, rej) => {
7040
+ const r = store.getAll();
7041
+ r.onsuccess = () => res(r.result);
7042
+ r.onerror = () => rej(r.error);
7043
+ });
7044
+ db.close();
7045
+ const found = pick(rows);
7046
+ if (found) return { found };
7047
+ } else {
7048
+ db.close();
7049
+ poisoned = true;
7050
+ }
7051
+ }
7052
+ }
7053
+ const lsRows = [];
7054
+ for (let i = 0; i < localStorage.length; i++) {
7055
+ const k = localStorage.key(i);
7056
+ if (k && k.startsWith('firebase:authUser:AIza')) {
7057
+ try { lsRows.push({ fbase_key: k, value: JSON.parse(localStorage.getItem(k)) }); } catch (e) {}
7058
+ }
7059
+ }
7060
+ const fromLs = pick(lsRows);
7061
+ if (fromLs) return { found: fromLs };
7062
+ return poisoned ? { poisoned: true } : null;
7063
+ } catch (e) {
7064
+ return null;
7065
+ }
7066
+ }`;
7067
+ var HEAL_POISONED_DB_PAGE_FN = `() => new Promise((resolve) => {
7068
+ const r = indexedDB.deleteDatabase('firebaseLocalStorageDb');
7069
+ const t = setTimeout(() => resolve('timeout'), 5000);
7070
+ r.onsuccess = () => { clearTimeout(t); resolve('deleted'); };
7071
+ r.onblocked = () => { clearTimeout(t); resolve('blocked'); };
7072
+ r.onerror = () => { clearTimeout(t); resolve('error'); };
7073
+ })`;
7074
+ var EXTRACT_FIREBASE_EXPRESSION = `(${EXTRACT_FIREBASE_PAGE_FN})()`;
7075
+ var HEAL_POISONED_DB_EXPRESSION = `(${HEAL_POISONED_DB_PAGE_FN})()`;
7076
+ var HELPER_LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
7077
+ var HEADLESS_PROBE_TIMEOUT_MS = 30 * 1e3;
7078
+ var CAPTURED_FRESH_MS = 10 * 60 * 1e3;
7079
+ var WAITING_FRESH_MS = HELPER_LOGIN_TIMEOUT_MS + 30 * 1e3;
7080
+ function helperEntryPath() {
7081
+ return path4.join(__dirname, "capture-helper.js");
7082
+ }
7083
+ function spawnCaptureHelper() {
7084
+ const entry = helperEntryPath();
7085
+ if (!fs4.existsSync(entry)) {
7086
+ return {
7087
+ ok: false,
7088
+ error: `capture helper not found at ${entry} (broken install?)`
7089
+ };
7090
+ }
7091
+ try {
7092
+ const child = (0, import_child_process.spawn)(process.execPath, [entry], {
7093
+ detached: true,
7094
+ stdio: "ignore",
7095
+ env: process.env
7096
+ });
7097
+ child.unref();
7098
+ return { ok: true };
7099
+ } catch (e) {
7100
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
7101
+ }
7102
+ }
7103
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
7104
+ async function pollCaptureState(budgetMs, intervalMs = 2e3) {
7105
+ const deadline = Date.now() + budgetMs;
7106
+ let last = null;
7107
+ for (; ; ) {
7108
+ last = readCaptureState();
7109
+ if (last && last.status !== "waiting_login") return last;
7110
+ if (Date.now() >= deadline) return last;
7111
+ await sleep2(Math.min(intervalMs, Math.max(1, deadline - Date.now())));
7112
+ }
7113
+ }
7114
+
6972
7115
  // src/setup-tool.ts
6973
7116
  var LICENSE_API = "https://elitedcs.com/api/validate-license";
6974
7117
  var CAPTURE_API = "https://elitedcs.com/api/capture-lead";
@@ -7055,21 +7198,21 @@ async function validateFirebase(firebaseKey, refreshToken) {
7055
7198
  function registerSetupTool(server2) {
7056
7199
  server2.tool(
7057
7200
  "setup_ghl_mcp",
7058
- "First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 228 tools (176 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7201
+ "First-run setup for GHL Command MCP. Validates your license and GHL credentials, then writes them to a per-user credentials file. Restart Claude after this completes to load all 229 tools (177 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
7059
7202
  {
7060
- email: import_zod38.z.string().email().describe("Email used at purchase."),
7061
- license_key: import_zod38.z.string().min(20).describe("License key from your purchase email."),
7062
- ghl_api_key: import_zod38.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
7063
- ghl_location_id: import_zod38.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
7064
- ghl_company_id: import_zod38.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
7203
+ email: import_zod39.z.string().email().describe("Email used at purchase."),
7204
+ license_key: import_zod39.z.string().min(20).describe("License key from your purchase email."),
7205
+ ghl_api_key: import_zod39.z.string().min(10).describe("GHL Private Integration key (starts with 'pit-'). Created INSIDE the sub-account at Settings > Integrations > Private Integrations."),
7206
+ ghl_location_id: import_zod39.z.string().min(10).describe("GHL Location ID (sub-account ID). Found in your GHL URL: /location/THIS_PART/dashboard."),
7207
+ ghl_company_id: import_zod39.z.string().optional().describe("(Agency only) Company ID for multi-location access."),
7065
7208
  // v3.25.0: one-paste shortcut. Run `auto_capture_firebase_script` first;
7066
7209
  // it returns a console script that fills the clipboard with this exact
7067
7210
  // JSON payload. Pasting it here removes the need to fill ghl_user_id,
7068
7211
  // ghl_firebase_api_key, and ghl_firebase_refresh_token individually.
7069
- firebase_paste: import_zod38.z.string().optional().describe("(Workflow Builder, one-paste path) Paste the JSON output from auto_capture_firebase_script here. Replaces the three separate Firebase fields below."),
7070
- ghl_user_id: import_zod38.z.string().optional().describe("(Workflow Builder, manual path) Firebase User ID. Prefer firebase_paste instead."),
7071
- ghl_firebase_api_key: import_zod38.z.string().optional().describe("(Workflow Builder, manual path) Firebase API Key starting with 'AIza'. Prefer firebase_paste instead."),
7072
- ghl_firebase_refresh_token: import_zod38.z.string().optional().describe("(Workflow Builder, manual path) Firebase refresh token. Prefer firebase_paste instead.")
7212
+ firebase_paste: import_zod39.z.string().optional().describe("(Workflow Builder, one-paste path) Paste the JSON output from auto_capture_firebase_script here. Replaces the three separate Firebase fields below."),
7213
+ ghl_user_id: import_zod39.z.string().optional().describe("(Workflow Builder, manual path) Firebase User ID. Prefer firebase_paste instead."),
7214
+ ghl_firebase_api_key: import_zod39.z.string().optional().describe("(Workflow Builder, manual path) Firebase API Key starting with 'AIza'. Prefer firebase_paste instead."),
7215
+ ghl_firebase_refresh_token: import_zod39.z.string().optional().describe("(Workflow Builder, manual path) Firebase refresh token. Prefer firebase_paste instead.")
7073
7216
  },
7074
7217
  async (args) => {
7075
7218
  const lic = await validateLicense(args.email, args.license_key);
@@ -7136,7 +7279,7 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7136
7279
  // verified on every MCP startup. Closes the hand-crafted creds bypass.
7137
7280
  signed_attestation: lic.signedAttestation
7138
7281
  });
7139
- const toolCount = workflowBuilderEnabled ? "228" : "176";
7282
+ const toolCount = workflowBuilderEnabled ? "229" : "177";
7140
7283
  const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
7141
7284
  const wfTip = workflowBuilderEnabled ? "" : "\nTo enable Workflow Builder later (49 extra Firebase-gated tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
7142
7285
  return {
@@ -7164,16 +7307,16 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
7164
7307
  function registerEnableWorkflowBuilderTool(server2) {
7165
7308
  server2.tool(
7166
7309
  "enable_workflow_builder",
7167
- "Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. Requires you've already run setup_ghl_mcp. EASIEST PATH: run `auto_capture_firebase_script` first, paste the JSON output into `firebase_paste` here. MANUAL PATH: capture the three Firebase fields via DevTools and pass them individually. Tool count goes from 176 to 228 after the next Claude restart.",
7310
+ "Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. Requires you've already run setup_ghl_mcp. EASIEST PATH: run `auto_capture_firebase_script` first, paste the JSON output into `firebase_paste` here. MANUAL PATH: capture the three Firebase fields via DevTools and pass them individually. Tool count goes from 177 to 229 after the next Claude restart.",
7168
7311
  {
7169
7312
  // v3.25.0: one-paste path. Tool runs `auto_capture_firebase_script` to
7170
7313
  // get the console script; the script returns a JSON object that pastes
7171
7314
  // cleanly into this field. Saves the buyer from picking out three
7172
7315
  // separate fields in IndexedDB.
7173
- firebase_paste: import_zod38.z.string().optional().describe("Paste the JSON output from auto_capture_firebase_script here. Replaces the three separate Firebase fields below."),
7174
- ghl_user_id: import_zod38.z.string().min(10).optional().describe("(Manual path) Firebase User ID (uid). Prefer firebase_paste."),
7175
- ghl_firebase_api_key: import_zod38.z.string().min(10).optional().describe("(Manual path) Firebase API Key starting with 'AIza'. Prefer firebase_paste."),
7176
- ghl_firebase_refresh_token: import_zod38.z.string().min(10).optional().describe("(Manual path) Firebase refresh token. Prefer firebase_paste.")
7316
+ firebase_paste: import_zod39.z.string().optional().describe("Paste the JSON output from auto_capture_firebase_script here. Replaces the three separate Firebase fields below."),
7317
+ ghl_user_id: import_zod39.z.string().min(10).optional().describe("(Manual path) Firebase User ID (uid). Prefer firebase_paste."),
7318
+ ghl_firebase_api_key: import_zod39.z.string().min(10).optional().describe("(Manual path) Firebase API Key starting with 'AIza'. Prefer firebase_paste."),
7319
+ ghl_firebase_refresh_token: import_zod39.z.string().min(10).optional().describe("(Manual path) Firebase refresh token. Prefer firebase_paste.")
7177
7320
  },
7178
7321
  async (args) => {
7179
7322
  const existing = readCredentials();
@@ -7245,7 +7388,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
7245
7388
  "",
7246
7389
  "**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen. Without a restart, the workflow builder tools will keep using the OLD Firebase auth from before this call and fail with 401 errors \u2014 even though this tool reported success.",
7247
7390
  "",
7248
- 'After restart, all 228 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
7391
+ 'After restart, all 229 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
7249
7392
  "",
7250
7393
  "Note: Firebase refresh tokens rotate every few weeks. If workflow tools stop working in a few weeks (run `health_check` to confirm Firebase auth: FAIL), run `auto_capture_firebase_script` for fresh values and re-run this tool with the new firebase_paste."
7251
7394
  ].join("\n")
@@ -7291,13 +7434,144 @@ function registerFirebaseCaptureScriptTool(server2) {
7291
7434
  }
7292
7435
  );
7293
7436
  }
7437
+ function registerInteractiveCaptureTool(server2) {
7438
+ server2.tool(
7439
+ "capture_firebase_interactive",
7440
+ "One-click Workflow Builder unlock (EASIEST PATH \u2014 try this before auto_capture_firebase_script). Opens a Chrome window where you just log into GHL; the MCP captures the Firebase credentials itself and saves them. No DevTools, no pasting. Re-run the same tool if the first call says it's still waiting for your login. After the first successful run, future re-captures (refresh-token rotations) happen silently with no window at all. Falls back cleanly: if no Chrome/Edge is installed, use auto_capture_firebase_script instead.",
7441
+ {},
7442
+ async () => {
7443
+ const existing = readCredentials();
7444
+ if (!existing) {
7445
+ return {
7446
+ content: [{
7447
+ type: "text",
7448
+ text: "No existing credentials found at " + credentialsPath() + ".\n\nRun setup_ghl_mcp first to register your license and basic GHL credentials, then run capture_firebase_interactive to unlock the Workflow Builder."
7449
+ }],
7450
+ isError: true
7451
+ };
7452
+ }
7453
+ const persistCaptured = async (state) => {
7454
+ const fbApi = state.ghl_firebase_api_key;
7455
+ const fbRefresh = state.ghl_firebase_refresh_token;
7456
+ const userId = state.ghl_user_id;
7457
+ const fb = await validateFirebase(fbApi, fbRefresh);
7458
+ if (!fb.ok) {
7459
+ clearCaptureState();
7460
+ return {
7461
+ content: [{
7462
+ type: "text",
7463
+ text: `Captured credentials were rejected by Firebase: ${fb.error}
7464
+
7465
+ The login in the capture window may belong to the wrong GHL account, or the session is stale. Run capture_firebase_interactive again and log into the SAME account you use with GHL Command.`
7466
+ }],
7467
+ isError: true
7468
+ };
7469
+ }
7470
+ writeCredentials({
7471
+ ...existing,
7472
+ ghl_user_id: userId,
7473
+ ghl_firebase_api_key: fbApi,
7474
+ ghl_firebase_refresh_token: fbRefresh
7475
+ });
7476
+ clearCaptureState();
7477
+ return {
7478
+ content: [{
7479
+ type: "text",
7480
+ text: [
7481
+ "Workflow Builder enabled!",
7482
+ "",
7483
+ `Firebase credentials captured${state.account_email ? ` from ${state.account_email}` : ""}, verified, and saved to credentials.json.`,
7484
+ "",
7485
+ "**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen.",
7486
+ "",
7487
+ 'After restart, all 229 tools load. Try: "List my workflows in full detail".',
7488
+ "",
7489
+ "Future token rotations re-capture silently \u2014 if workflow tools ever 401, just run capture_firebase_interactive again; no window should appear."
7490
+ ].join("\n")
7491
+ }]
7492
+ };
7493
+ };
7494
+ const fallbackNote = "Fallback that always works: run `auto_capture_firebase_script` and paste the script into Chrome's Console on a logged-in GHL tab (type `allow pasting` first if Chrome blocks it).";
7495
+ const prior = readCaptureState();
7496
+ if (prior?.status === "captured" && !isStale(prior, CAPTURED_FRESH_MS)) {
7497
+ return persistCaptured(prior);
7498
+ }
7499
+ if (prior?.status === "waiting_login" && !isStale(prior, WAITING_FRESH_MS)) {
7500
+ const polled2 = await pollCaptureState(6e4);
7501
+ if (polled2?.status === "captured") return persistCaptured(polled2);
7502
+ if (polled2?.status === "error") {
7503
+ clearCaptureState();
7504
+ return {
7505
+ content: [{ type: "text", text: `Capture failed: ${polled2.error}
7506
+
7507
+ ${fallbackNote}` }],
7508
+ isError: true
7509
+ };
7510
+ }
7511
+ return {
7512
+ content: [{
7513
+ type: "text",
7514
+ text: "The capture window is still open and waiting for your GHL login. Finish logging in (2FA is fine), leave the window alone for a few seconds, then run capture_firebase_interactive again to save the result."
7515
+ }]
7516
+ };
7517
+ }
7518
+ clearCaptureState();
7519
+ const spawned = spawnCaptureHelper();
7520
+ if (!spawned.ok) {
7521
+ return {
7522
+ content: [{ type: "text", text: `Couldn't start the capture helper: ${spawned.error}
7523
+
7524
+ ${fallbackNote}` }],
7525
+ isError: true
7526
+ };
7527
+ }
7528
+ const polled = await pollCaptureState(75e3);
7529
+ if (polled?.status === "captured") return persistCaptured(polled);
7530
+ if (polled?.status === "error") {
7531
+ clearCaptureState();
7532
+ return {
7533
+ content: [{ type: "text", text: `Capture failed: ${polled.error}
7534
+
7535
+ ${fallbackNote}` }],
7536
+ isError: true
7537
+ };
7538
+ }
7539
+ if (polled?.status === "timeout") {
7540
+ clearCaptureState();
7541
+ return {
7542
+ content: [{
7543
+ type: "text",
7544
+ text: `The capture window timed out waiting for a login. Run capture_firebase_interactive again when you're ready to log in.
7545
+
7546
+ ${fallbackNote}`
7547
+ }],
7548
+ isError: true
7549
+ };
7550
+ }
7551
+ return {
7552
+ content: [{
7553
+ type: "text",
7554
+ text: [
7555
+ `A Chrome window just opened on the GoHighLevel login page (it may say "Chrome is being controlled by automated test software" \u2014 that's this tool).`,
7556
+ "",
7557
+ "**Log into your GHL account in that window** (2FA is fine). That's your entire job \u2014 the tool reads the credentials itself.",
7558
+ "",
7559
+ "Once you're logged in, run `capture_firebase_interactive` again and it will save the result. You only ever do this login once: future re-captures happen silently.",
7560
+ "",
7561
+ "Don't see a window? " + fallbackNote
7562
+ ].join("\n")
7563
+ }]
7564
+ };
7565
+ }
7566
+ );
7567
+ }
7294
7568
  function registerLeadCaptureTool(server2) {
7295
7569
  server2.tool(
7296
7570
  "request_license",
7297
7571
  "Get a GHL Command license. Use this if you installed from npm but don't have a license yet (or setup_ghl_mcp says your license is missing/invalid). GHL Command is $97/mo \u2014 every sub-account you manage, 3-machine activation, includes the only programmatic GHL workflow builder. Leave your email and we'll send the purchase link + setup help; the tool also returns where to buy right now.",
7298
7572
  {
7299
- email: import_zod38.z.string().email().describe("Your email \u2014 where to send the purchase link and setup help."),
7300
- name: import_zod38.z.string().optional().describe("Your name (optional).")
7573
+ email: import_zod39.z.string().email().describe("Your email \u2014 where to send the purchase link and setup help."),
7574
+ name: import_zod39.z.string().optional().describe("Your name (optional).")
7301
7575
  },
7302
7576
  async (args) => {
7303
7577
  const buyUrl = "https://ghlcommand.com";
@@ -7416,7 +7690,7 @@ Token registry: ${registeredCount} location(s) registered${versionLine}`
7416
7690
  "switch_location",
7417
7691
  "Switch the active GHL sub-account. Automatically swaps the API key from the token registry if available. After switching, all tools default to the new location.",
7418
7692
  {
7419
- locationId: import_zod39.z.string().describe("The Location ID to switch to.")
7693
+ locationId: import_zod40.z.string().describe("The Location ID to switch to.")
7420
7694
  },
7421
7695
  async ({ locationId: locationId2 }) => withSwitchLock(async () => {
7422
7696
  const previousId = client.defaultLocationId;
@@ -7489,9 +7763,9 @@ Still on: ${previousId || "none"}${hint}` }],
7489
7763
  "register_location",
7490
7764
  "Add a GHL sub-account to the token registry so switch_location can automatically use its API key. Each sub-account needs its own Private Integration key created in GHL Settings > Integrations.",
7491
7765
  {
7492
- locationId: import_zod39.z.string().describe("The GHL Location ID (from Settings > Business Profile)."),
7493
- name: import_zod39.z.string().describe("A friendly name for this sub-account (e.g. 'PNTracker', 'Med Spa Template')."),
7494
- apiKey: import_zod39.z.string().describe("The Private Integration API key for this sub-account (starts with 'pit-').")
7766
+ locationId: import_zod40.z.string().describe("The GHL Location ID (from Settings > Business Profile)."),
7767
+ name: import_zod40.z.string().describe("A friendly name for this sub-account (e.g. 'PNTracker', 'Med Spa Template')."),
7768
+ apiKey: import_zod40.z.string().describe("The Private Integration API key for this sub-account (starts with 'pit-').")
7495
7769
  },
7496
7770
  async ({ locationId: locationId2, name, apiKey: apiKey2 }) => {
7497
7771
  if (!registry2) {
@@ -7548,7 +7822,7 @@ The API key could not access location ${locationId2}. Make sure:
7548
7822
  "register_agency_key",
7549
7823
  "Store the AGENCY-level (company-scoped) API key in the token registry. This key powers agency-wide tools: list_snapshots, create_snapshot_share_link, and list_available_locations across all sub-accounts. Create it at the AGENCY level in GHL (Agency Settings > Private Integrations) \u2014 it is different from a sub-account's key. The key is validated before saving.",
7550
7824
  {
7551
- apiKey: import_zod39.z.string().describe("The agency-level Private Integration API key (starts with 'pit-'). Must be created in AGENCY settings, not inside a sub-account.")
7825
+ apiKey: import_zod40.z.string().describe("The agency-level Private Integration API key (starts with 'pit-'). Must be created in AGENCY settings, not inside a sub-account.")
7552
7826
  },
7553
7827
  async ({ apiKey: apiKey2 }) => {
7554
7828
  if (!registry2) {
@@ -7602,7 +7876,7 @@ Agency-wide tools now available: list_snapshots, create_snapshot_share_link, and
7602
7876
  "unregister_location",
7603
7877
  "Remove a GHL sub-account from the token registry.",
7604
7878
  {
7605
- locationId: import_zod39.z.string().describe("The Location ID to remove.")
7879
+ locationId: import_zod40.z.string().describe("The Location ID to remove.")
7606
7880
  },
7607
7881
  async ({ locationId: locationId2 }) => {
7608
7882
  if (!registry2) {
@@ -7628,12 +7902,12 @@ Agency-wide tools now available: list_snapshots, create_snapshot_share_link, and
7628
7902
  "register_company_firebase",
7629
7903
  "Register a GHL company's Firebase credentials so the workflow builder and all Firebase-gated tools work when you switch into THAT company's sub-accounts. Firebase refresh tokens are company-scoped, so managing a client's GHL (e.g. an account where you're an admin user) requires that company's own token. Capture the values from a browser session logged into the client's account. The tool stores them under the company ID the Firebase token itself authenticates as (decoded from the token), so you do NOT need to hunt down the exact internal company ID \u2014 pass whatever ID you have and it self-corrects. After this, switch_location to any of that company's locations authenticates the workflow builder correctly. DevTools capture steps: elitedcs.com/ghl-mcp-firebase.",
7630
7904
  {
7631
- companyId: import_zod39.z.string().describe("A GHL company/agency ID for this client (from switch_location/register_location output, or the GHL agency URL). Best-effort only: the tool re-keys the entry to the company ID the Firebase token actually authenticates as, which can differ from the ID shown in the agency URL. Just pass what you have."),
7632
- name: import_zod39.z.string().describe("Friendly name for this client/company (e.g. 'Nathan \u2014 Acme Health')."),
7633
- ghl_firebase_refresh_token: import_zod39.z.string().min(10).describe("Firebase refresh token captured from a browser session logged into THIS company's GHL. value.stsTokenManager.refreshToken in the firebase:authUser IndexedDB row."),
7634
- ghl_user_id: import_zod39.z.string().min(5).describe("Firebase User ID (uid) from the same session. value.uid in the firebase:authUser row."),
7635
- ghl_firebase_api_key: import_zod39.z.string().optional().describe("Firebase API key (starts with 'AIza'). Optional \u2014 defaults to your home Firebase API key, which is identical across GHL accounts."),
7636
- test_location_id: import_zod39.z.string().optional().describe("Optional but recommended: a registered location ID belonging to this company. The tool then makes a real workflow-builder call to confirm these credentials actually work for this company before you rely on them.")
7905
+ companyId: import_zod40.z.string().describe("A GHL company/agency ID for this client (from switch_location/register_location output, or the GHL agency URL). Best-effort only: the tool re-keys the entry to the company ID the Firebase token actually authenticates as, which can differ from the ID shown in the agency URL. Just pass what you have."),
7906
+ name: import_zod40.z.string().describe("Friendly name for this client/company (e.g. 'Nathan \u2014 Acme Health')."),
7907
+ ghl_firebase_refresh_token: import_zod40.z.string().min(10).describe("Firebase refresh token captured from a browser session logged into THIS company's GHL. value.stsTokenManager.refreshToken in the firebase:authUser IndexedDB row."),
7908
+ ghl_user_id: import_zod40.z.string().min(5).describe("Firebase User ID (uid) from the same session. value.uid in the firebase:authUser row."),
7909
+ ghl_firebase_api_key: import_zod40.z.string().optional().describe("Firebase API key (starts with 'AIza'). Optional \u2014 defaults to your home Firebase API key, which is identical across GHL accounts."),
7910
+ test_location_id: import_zod40.z.string().optional().describe("Optional but recommended: a registered location ID belonging to this company. The tool then makes a real workflow-builder call to confirm these credentials actually work for this company before you rely on them.")
7637
7911
  },
7638
7912
  async (args) => {
7639
7913
  if (!registry2) {
@@ -7737,7 +8011,7 @@ Now run switch_location to any of this company's sub-accounts \u2014 the workflo
7737
8011
  "unregister_company_firebase",
7738
8012
  "Remove a company's Firebase credentials from the registry. Workflow-builder tools will stop working for that company's sub-accounts until re-registered.",
7739
8013
  {
7740
- companyId: import_zod39.z.string().describe("The company ID to remove Firebase credentials for.")
8014
+ companyId: import_zod40.z.string().describe("The company ID to remove Firebase credentials for.")
7741
8015
  },
7742
8016
  async ({ companyId }) => {
7743
8017
  if (!registry2) {
@@ -7805,8 +8079,8 @@ ${lines.join("\n")}
7805
8079
  "list_available_locations",
7806
8080
  "List all GHL sub-accounts (locations) accessible with the current or agency API key. Shows locations that exist in the GHL account \u2014 use register_location to add their tokens. Offset-based pagination via skip/limit.",
7807
8081
  {
7808
- limit: import_zod39.z.number().optional().describe("Max locations to return. Defaults to 20."),
7809
- skip: import_zod39.z.number().optional().describe("Number to skip for pagination.")
8082
+ limit: import_zod40.z.number().optional().describe("Max locations to return. Defaults to 20."),
8083
+ skip: import_zod40.z.number().optional().describe("Number to skip for pagination.")
7810
8084
  },
7811
8085
  async ({ limit, skip }) => {
7812
8086
  try {
@@ -7849,7 +8123,7 @@ ${lines.join("\n")}
7849
8123
  }
7850
8124
 
7851
8125
  // src/tools/bulk-operations.ts
7852
- var import_zod40 = require("zod");
8126
+ var import_zod41 = require("zod");
7853
8127
  function delay(ms) {
7854
8128
  return new Promise((resolve5) => setTimeout(resolve5, ms));
7855
8129
  }
@@ -7861,8 +8135,8 @@ function registerBulkOperationTools(server2, client) {
7861
8135
  "bulk_add_tags",
7862
8136
  "Add tags to multiple contacts at once. Rate-limited to avoid API throttling. Returns a summary of successes and failures.",
7863
8137
  {
7864
- contactIds: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to tag."),
7865
- tags: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one tag required.").describe("Tags to add to each contact.")
8138
+ contactIds: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to tag."),
8139
+ tags: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one tag required.").describe("Tags to add to each contact.")
7866
8140
  },
7867
8141
  async ({ contactIds, tags }) => {
7868
8142
  const results = { success: 0, failed: 0, errors: [] };
@@ -7884,8 +8158,8 @@ function registerBulkOperationTools(server2, client) {
7884
8158
  "bulk_remove_tags",
7885
8159
  "Remove tags from multiple contacts at once. Rate-limited.",
7886
8160
  {
7887
- contactIds: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs."),
7888
- tags: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one tag required.").describe("Tags to remove from each contact.")
8161
+ contactIds: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs."),
8162
+ tags: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one tag required.").describe("Tags to remove from each contact.")
7889
8163
  },
7890
8164
  async ({ contactIds, tags }) => {
7891
8165
  const results = { success: 0, failed: 0, errors: [] };
@@ -7906,8 +8180,8 @@ function registerBulkOperationTools(server2, client) {
7906
8180
  "bulk_update_contacts",
7907
8181
  "Update the same field(s) on multiple contacts at once. Rate-limited. Example: set a custom field value, change source, update address for a batch of contacts.",
7908
8182
  {
7909
- contactIds: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to update."),
7910
- fields: import_zod40.z.record(import_zod40.z.unknown()).describe("Fields to set on each contact (e.g. {customField: {id: 'xxx', value: 'yyy'}}, {source: 'Import'}).")
8183
+ contactIds: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to update."),
8184
+ fields: import_zod41.z.record(import_zod41.z.unknown()).describe("Fields to set on each contact (e.g. {customField: {id: 'xxx', value: 'yyy'}}, {source: 'Import'}).")
7911
8185
  },
7912
8186
  async ({ contactIds, fields }) => {
7913
8187
  const results = { success: 0, failed: 0, errors: [] };
@@ -7928,8 +8202,8 @@ function registerBulkOperationTools(server2, client) {
7928
8202
  "bulk_add_to_workflow",
7929
8203
  "Enroll multiple contacts into a workflow at once. Rate-limited.",
7930
8204
  {
7931
- contactIds: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to enroll."),
7932
- workflowId: import_zod40.z.string().describe("The workflow ID to enroll contacts into.")
8205
+ contactIds: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to enroll."),
8206
+ workflowId: import_zod41.z.string().describe("The workflow ID to enroll contacts into.")
7933
8207
  },
7934
8208
  async ({ contactIds, workflowId }) => {
7935
8209
  const results = { success: 0, failed: 0, errors: [] };
@@ -7950,8 +8224,8 @@ function registerBulkOperationTools(server2, client) {
7950
8224
  "bulk_delete_contacts",
7951
8225
  "Delete multiple contacts at once. IRREVERSIBLE. Rate-limited. Use with extreme caution.",
7952
8226
  {
7953
- contactIds: import_zod40.z.array(import_zod40.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to permanently delete."),
7954
- confirm: import_zod40.z.literal("DELETE").describe("Must pass the string 'DELETE' to confirm. This is a safety check.")
8227
+ contactIds: import_zod41.z.array(import_zod41.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to permanently delete."),
8228
+ confirm: import_zod41.z.literal("DELETE").describe("Must pass the string 'DELETE' to confirm. This is a safety check.")
7955
8229
  },
7956
8230
  async ({ contactIds, confirm }) => {
7957
8231
  if (confirm !== "DELETE") {
@@ -7974,7 +8248,7 @@ function registerBulkOperationTools(server2, client) {
7974
8248
  }
7975
8249
 
7976
8250
  // src/tools/account-export.ts
7977
- var import_zod41 = require("zod");
8251
+ var import_zod42 = require("zod");
7978
8252
  function delay2(ms) {
7979
8253
  return new Promise((resolve5) => setTimeout(resolve5, ms));
7980
8254
  }
@@ -7984,8 +8258,8 @@ function registerAccountExportTools(server2, client) {
7984
8258
  "export_account",
7985
8259
  "Export a complete inventory of the GHL sub-account: location info, contacts (count + sample), pipelines with stages, workflows (with full actions if builder auth is configured), funnels with pages, forms, custom fields, custom values, tags, calendars, and users. Returns a comprehensive JSON report for auditing or backup.",
7986
8260
  {
7987
- locationId: import_zod41.z.string().optional().describe("Location ID to export. Uses default if not specified."),
7988
- includeContacts: import_zod41.z.boolean().optional().describe("Include contact list (first 100). Defaults to false for speed.")
8261
+ locationId: import_zod42.z.string().optional().describe("Location ID to export. Uses default if not specified."),
8262
+ includeContacts: import_zod42.z.boolean().optional().describe("Include contact list (first 100). Defaults to false for speed.")
7989
8263
  },
7990
8264
  async ({ locationId: locationId2, includeContacts }) => {
7991
8265
  try {
@@ -8113,8 +8387,8 @@ function registerAccountExportTools(server2, client) {
8113
8387
  "compare_locations",
8114
8388
  "Compare two GHL sub-accounts side by side \u2014 shows differences in pipelines, workflows, custom fields, tags, forms, and funnels. Useful for ensuring consistency across locations or auditing before/after changes.",
8115
8389
  {
8116
- locationA: import_zod41.z.string().describe("First Location ID."),
8117
- locationB: import_zod41.z.string().describe("Second Location ID.")
8390
+ locationA: import_zod42.z.string().describe("First Location ID."),
8391
+ locationB: import_zod42.z.string().describe("Second Location ID.")
8118
8392
  },
8119
8393
  async ({ locationA, locationB }) => {
8120
8394
  try {
@@ -8192,7 +8466,7 @@ function registerAccountExportTools(server2, client) {
8192
8466
  }
8193
8467
 
8194
8468
  // src/tools/workflow-cloner.ts
8195
- var import_zod42 = require("zod");
8469
+ var import_zod43 = require("zod");
8196
8470
  var crypto3 = __toESM(require("crypto"));
8197
8471
  function registerWorkflowClonerTools(server2, builderClient) {
8198
8472
  const client = builderClient;
@@ -8201,8 +8475,8 @@ function registerWorkflowClonerTools(server2, builderClient) {
8201
8475
  "clone_workflow",
8202
8476
  "Deep clone a workflow \u2014 creates an exact copy with new IDs for all actions, triggers, and references. The clone starts as a draft. Useful for creating templates or duplicating workflows across projects.",
8203
8477
  {
8204
- sourceWorkflowId: import_zod42.z.string().describe("The workflow ID to clone."),
8205
- newName: import_zod42.z.string().describe("Name for the cloned workflow.")
8478
+ sourceWorkflowId: import_zod43.z.string().describe("The workflow ID to clone."),
8479
+ newName: import_zod43.z.string().describe("Name for the cloned workflow.")
8206
8480
  },
8207
8481
  async ({ sourceWorkflowId, newName }) => {
8208
8482
  try {
@@ -8291,15 +8565,15 @@ function registerWorkflowClonerTools(server2, builderClient) {
8291
8565
  }
8292
8566
 
8293
8567
  // src/tools/smart-lists.ts
8294
- var import_zod43 = require("zod");
8568
+ var import_zod44 = require("zod");
8295
8569
  var SMARTLIST_BASE = "https://backend.leadconnectorhq.com/lists/dynamic";
8296
8570
  var OBJECT_KEYS = ["contacts", "opportunity"];
8297
8571
  function registerSmartListTools(server2, builderClient) {
8298
8572
  const client = builderClient;
8299
8573
  if (!client) return;
8300
- async function smartListRequest(method, path7, body) {
8574
+ async function smartListRequest(method, path8, body) {
8301
8575
  const headers = await client.buildHeaders();
8302
- const url = `${SMARTLIST_BASE}${path7}`;
8576
+ const url = `${SMARTLIST_BASE}${path8}`;
8303
8577
  const options = { method, headers };
8304
8578
  if (body && (method === "POST" || method === "PUT")) {
8305
8579
  options.body = JSON.stringify(body);
@@ -8307,7 +8581,7 @@ function registerSmartListTools(server2, builderClient) {
8307
8581
  const response = await fetch(url, options);
8308
8582
  if (!response.ok) {
8309
8583
  const text2 = await response.text();
8310
- throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path7}
8584
+ throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path8}
8311
8585
  ${text2}`);
8312
8586
  }
8313
8587
  const text = await response.text();
@@ -8318,11 +8592,11 @@ ${text2}`);
8318
8592
  "list_smart_lists",
8319
8593
  "List smart lists (dynamic / saved-filter lists) in a location. Smart Lists are saved searches over contacts or opportunities \u2014 agencies use them to segment by complex criteria. Filters and columns aren't returned in the list view; use get_smart_list for the full filter spec.",
8320
8594
  {
8321
- objectKey: import_zod43.z.enum(OBJECT_KEYS).describe("The object type the lists segment over. 'contacts' for contact-segments, 'opportunity' for opportunity-segments. Required \u2014 GHL rejects requests without it."),
8322
- query: import_zod43.z.string().optional().describe("Free-text search across smart list names."),
8323
- limit: import_zod43.z.number().optional().describe("Max smart lists per page. Defaults to 20 on GHL's side."),
8324
- startAfter: import_zod43.z.string().optional().describe("Cursor for pagination \u2014 pass the last list's id from the previous page."),
8325
- locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8595
+ objectKey: import_zod44.z.enum(OBJECT_KEYS).describe("The object type the lists segment over. 'contacts' for contact-segments, 'opportunity' for opportunity-segments. Required \u2014 GHL rejects requests without it."),
8596
+ query: import_zod44.z.string().optional().describe("Free-text search across smart list names."),
8597
+ limit: import_zod44.z.number().optional().describe("Max smart lists per page. Defaults to 20 on GHL's side."),
8598
+ startAfter: import_zod44.z.string().optional().describe("Cursor for pagination \u2014 pass the last list's id from the previous page."),
8599
+ locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8326
8600
  },
8327
8601
  async ({ objectKey, query, limit, startAfter, locationId: locationId2 }) => {
8328
8602
  try {
@@ -8342,8 +8616,8 @@ ${text2}`);
8342
8616
  "get_smart_list",
8343
8617
  "Get a single smart list by ID with its full configuration: filters, columns, permissions, and metadata. The filters array is what defines who/what is in the list.",
8344
8618
  {
8345
- listId: import_zod43.z.string().describe("The smart list ID (from list_smart_lists or a previous create_smart_list response)."),
8346
- locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8619
+ listId: import_zod44.z.string().describe("The smart list ID (from list_smart_lists or a previous create_smart_list response)."),
8620
+ locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8347
8621
  },
8348
8622
  async ({ listId, locationId: locationId2 }) => {
8349
8623
  try {
@@ -8359,13 +8633,13 @@ ${text2}`);
8359
8633
  "create_smart_list",
8360
8634
  "Create a new smart list (dynamic filter list). Required: name + objectKey. Filters define the saved-search criteria \u2014 pass an empty array to create an empty list and add filters later via update_smart_list. The shape of filters/columns is opaque here; query an existing smart list with get_smart_list to see the format GHL expects.",
8361
8635
  {
8362
- name: import_zod43.z.string().describe("Display name for the smart list."),
8363
- objectKey: import_zod43.z.enum(OBJECT_KEYS).describe("Object type the list segments over. 'contacts' or 'opportunity'."),
8364
- filters: import_zod43.z.array(import_zod43.z.record(import_zod43.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
8365
- columns: import_zod43.z.array(import_zod43.z.record(import_zod43.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
8366
- pipelineIds: import_zod43.z.array(import_zod43.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs to restrict this smart list to. Empty array = all pipelines."),
8367
- defaultInPipelines: import_zod43.z.array(import_zod43.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs where this list is the default view."),
8368
- locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8636
+ name: import_zod44.z.string().describe("Display name for the smart list."),
8637
+ objectKey: import_zod44.z.enum(OBJECT_KEYS).describe("Object type the list segments over. 'contacts' or 'opportunity'."),
8638
+ filters: import_zod44.z.array(import_zod44.z.record(import_zod44.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
8639
+ columns: import_zod44.z.array(import_zod44.z.record(import_zod44.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
8640
+ pipelineIds: import_zod44.z.array(import_zod44.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs to restrict this smart list to. Empty array = all pipelines."),
8641
+ defaultInPipelines: import_zod44.z.array(import_zod44.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs where this list is the default view."),
8642
+ locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8369
8643
  },
8370
8644
  async ({ name, objectKey, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
8371
8645
  try {
@@ -8386,13 +8660,13 @@ ${text2}`);
8386
8660
  "update_smart_list",
8387
8661
  "Update an existing smart list's name, filters, or columns. The objectKey CANNOT be changed after creation (GHL rejects with 422 if you try). Use get_smart_list first to inspect the current filters; partial updates work \u2014 pass only the fields you want to change.",
8388
8662
  {
8389
- listId: import_zod43.z.string().describe("The smart list ID to update."),
8390
- name: import_zod43.z.string().optional().describe("New display name."),
8391
- filters: import_zod43.z.array(import_zod43.z.record(import_zod43.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
8392
- columns: import_zod43.z.array(import_zod43.z.record(import_zod43.z.unknown())).optional().describe("Replace the column array entirely."),
8393
- pipelineIds: import_zod43.z.array(import_zod43.z.string()).optional().describe("(opportunity only) Update the pipeline scope."),
8394
- defaultInPipelines: import_zod43.z.array(import_zod43.z.string()).optional().describe("(opportunity only) Update the default-in-pipelines list."),
8395
- locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8663
+ listId: import_zod44.z.string().describe("The smart list ID to update."),
8664
+ name: import_zod44.z.string().optional().describe("New display name."),
8665
+ filters: import_zod44.z.array(import_zod44.z.record(import_zod44.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
8666
+ columns: import_zod44.z.array(import_zod44.z.record(import_zod44.z.unknown())).optional().describe("Replace the column array entirely."),
8667
+ pipelineIds: import_zod44.z.array(import_zod44.z.string()).optional().describe("(opportunity only) Update the pipeline scope."),
8668
+ defaultInPipelines: import_zod44.z.array(import_zod44.z.string()).optional().describe("(opportunity only) Update the default-in-pipelines list."),
8669
+ locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8396
8670
  },
8397
8671
  async ({ listId, name, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
8398
8672
  try {
@@ -8417,9 +8691,9 @@ ${text2}`);
8417
8691
  "delete_smart_list",
8418
8692
  "Permanently delete a smart list. IRREVERSIBLE. The list configuration is removed but the contacts/opportunities themselves are NOT touched \u2014 smart lists are just saved filters. Any workflow trigger / dashboard / report that referenced this list by ID will stop working.",
8419
8693
  {
8420
- listId: import_zod43.z.string().describe("The smart list ID to delete."),
8421
- confirm: import_zod43.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
8422
- locationId: import_zod43.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8694
+ listId: import_zod44.z.string().describe("The smart list ID to delete."),
8695
+ confirm: import_zod44.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
8696
+ locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8423
8697
  },
8424
8698
  async ({ listId, locationId: locationId2 }) => {
8425
8699
  try {
@@ -8434,17 +8708,17 @@ ${text2}`);
8434
8708
  }
8435
8709
 
8436
8710
  // src/tools/reputation.ts
8437
- var import_zod44 = require("zod");
8711
+ var import_zod45 = require("zod");
8438
8712
  var REPUTATION_BASE = "https://backend.leadconnectorhq.com/reputation";
8439
8713
  function registerReputationTools(server2, builderClient) {
8440
8714
  const client = builderClient;
8441
8715
  if (!client) return;
8442
- async function reputationRequest(method, path7) {
8716
+ async function reputationRequest(method, path8) {
8443
8717
  const headers = await client.buildHeaders();
8444
- const response = await fetch(`${REPUTATION_BASE}${path7}`, { method, headers });
8718
+ const response = await fetch(`${REPUTATION_BASE}${path8}`, { method, headers });
8445
8719
  if (!response.ok) {
8446
8720
  const text2 = await response.text();
8447
- throw new Error(`Reputation API Error ${response.status}: ${method} ${path7}
8721
+ throw new Error(`Reputation API Error ${response.status}: ${method} ${path8}
8448
8722
  ${text2}`);
8449
8723
  }
8450
8724
  const text = await response.text();
@@ -8456,7 +8730,7 @@ ${text2}`);
8456
8730
  "get_review_link_list",
8457
8731
  "List the review-link destinations configured for a location \u2014 the platforms (Google, Facebook, etc.) where review requests send contacts. Each entry has a label and the public review URL. Useful for: building review-request workflows (the workflow goal condition `review_request_clicked` references these review-link ids), and auditing which review platforms a sub-account has connected.",
8458
8732
  {
8459
- locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8733
+ locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8460
8734
  },
8461
8735
  async ({ locationId: locationId2 }) => {
8462
8736
  const loc = locationId2 ?? client.locationId;
@@ -8468,11 +8742,11 @@ ${text2}`);
8468
8742
  "list_reviews",
8469
8743
  "List the reviews a location has received (Google, Facebook, etc.) with rating, author, text, reply status, and source. Supports paging and an optional rating filter. NOTE: location is resolved through nested filter params internally \u2014 a flat locationId is what caused the long-standing 'No Location Found' error, now fixed. Responding to a review is not yet available via API. Requires Firebase auth.",
8470
8744
  {
8471
- locationId: import_zod44.z.string().optional().describe("Location ID. Falls back to the active builder client's location."),
8472
- pageNumber: import_zod44.z.number().optional().describe("1-based page number. Defaults to 1."),
8473
- pageSize: import_zod44.z.number().optional().describe("Results per page. Defaults to 10."),
8474
- rating: import_zod44.z.number().optional().describe("Optional: only return reviews with this star rating (1-5)."),
8475
- includeDeleted: import_zod44.z.boolean().optional().describe("Include deleted reviews. Defaults to false.")
8745
+ locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location."),
8746
+ pageNumber: import_zod45.z.number().optional().describe("1-based page number. Defaults to 1."),
8747
+ pageSize: import_zod45.z.number().optional().describe("Results per page. Defaults to 10."),
8748
+ rating: import_zod45.z.number().optional().describe("Optional: only return reviews with this star rating (1-5)."),
8749
+ includeDeleted: import_zod45.z.boolean().optional().describe("Include deleted reviews. Defaults to false.")
8476
8750
  },
8477
8751
  async ({ locationId: locationId2, pageNumber, pageSize, rating, includeDeleted }) => {
8478
8752
  const loc = locationId2 ?? client.locationId;
@@ -8502,7 +8776,7 @@ function buildReviewsQuery(locationId2, opts = {}) {
8502
8776
  }
8503
8777
 
8504
8778
  // src/tools/email-campaigns.ts
8505
- var import_zod45 = require("zod");
8779
+ var import_zod46 = require("zod");
8506
8780
  var SVC_BASE = "https://services.leadconnectorhq.com";
8507
8781
  function registerEmailCampaignTools(server2, builderClient) {
8508
8782
  const client = builderClient;
@@ -8512,15 +8786,15 @@ function registerEmailCampaignTools(server2, builderClient) {
8512
8786
  "create_email_campaign",
8513
8787
  "Create an email campaign / broadcast DRAFT from an existing email template. Requires a templateId (create one first with create_email_template). The campaign is created as a draft \u2014 to actually SEND or schedule it, finish in the GHL UI: the send/schedule endpoint isn't available through the API yet. There's also no API delete for campaigns, so drafts created here are removed via the GHL UI. Despite those limits, this gets the campaign 90% built \u2014 template, subject, sender, name all set programmatically.",
8514
8788
  {
8515
- templateId: import_zod45.z.string().describe("ID of an email template (from create_email_template or list_email_templates) to use as the campaign body."),
8516
- name: import_zod45.z.string().optional().describe("Internal campaign name (shown in the campaigns list, not to recipients). Defaults to a GHL-generated name."),
8517
- subject: import_zod45.z.string().optional().describe("Email subject line recipients see."),
8518
- fromName: import_zod45.z.string().optional().describe("Sender display name."),
8519
- fromEmail: import_zod45.z.string().optional().describe("Sender email address. Must be a verified sending address in the location."),
8520
- isPlainText: import_zod45.z.boolean().optional().describe("Send as plain text instead of HTML. Defaults to false."),
8521
- enableResendToUnopened: import_zod45.z.boolean().optional().describe("Auto-resend to contacts who didn't open. Defaults to false."),
8522
- hasUtmTracking: import_zod45.z.boolean().optional().describe("Append UTM tracking params to links. Defaults to false."),
8523
- locationId: import_zod45.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8789
+ templateId: import_zod46.z.string().describe("ID of an email template (from create_email_template or list_email_templates) to use as the campaign body."),
8790
+ name: import_zod46.z.string().optional().describe("Internal campaign name (shown in the campaigns list, not to recipients). Defaults to a GHL-generated name."),
8791
+ subject: import_zod46.z.string().optional().describe("Email subject line recipients see."),
8792
+ fromName: import_zod46.z.string().optional().describe("Sender display name."),
8793
+ fromEmail: import_zod46.z.string().optional().describe("Sender email address. Must be a verified sending address in the location."),
8794
+ isPlainText: import_zod46.z.boolean().optional().describe("Send as plain text instead of HTML. Defaults to false."),
8795
+ enableResendToUnopened: import_zod46.z.boolean().optional().describe("Auto-resend to contacts who didn't open. Defaults to false."),
8796
+ hasUtmTracking: import_zod46.z.boolean().optional().describe("Append UTM tracking params to links. Defaults to false."),
8797
+ locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8524
8798
  },
8525
8799
  async ({ templateId, name, subject, fromName, fromEmail, isPlainText, enableResendToUnopened, hasUtmTracking, locationId: locationId2 }) => {
8526
8800
  const loc = locationId2 ?? client.locationId;
@@ -8554,21 +8828,21 @@ ${text2}`);
8554
8828
  }
8555
8829
 
8556
8830
  // src/tools/memberships.ts
8557
- var import_zod46 = require("zod");
8831
+ var import_zod47 = require("zod");
8558
8832
  var MEMBERSHIP_BASE = "https://backend.leadconnectorhq.com/membership";
8559
8833
  function registerMembershipTools(server2, builderClient) {
8560
8834
  const client = builderClient;
8561
8835
  if (!client) return;
8562
- async function membershipRequest(path7, method = "GET", body) {
8836
+ async function membershipRequest(path8, method = "GET", body) {
8563
8837
  const headers = await client.buildHeaders();
8564
- const response = await fetch(`${MEMBERSHIP_BASE}${path7}`, {
8838
+ const response = await fetch(`${MEMBERSHIP_BASE}${path8}`, {
8565
8839
  method,
8566
8840
  headers,
8567
8841
  body: body ? JSON.stringify(body) : void 0
8568
8842
  });
8569
8843
  if (!response.ok) {
8570
8844
  const text2 = await response.text();
8571
- throw new Error(`Membership API Error ${response.status}: ${method} ${path7}
8845
+ throw new Error(`Membership API Error ${response.status}: ${method} ${path8}
8572
8846
  ${text2}`);
8573
8847
  }
8574
8848
  const text = await response.text();
@@ -8580,7 +8854,7 @@ ${text2}`);
8580
8854
  "list_membership_offers",
8581
8855
  "List a location's membership offers and products in one call. Returns { products: [...], offers: [...] }. Products are courses/communities; offers are the access grants (what a contact gets enrolled in). Use the returned ids with membership trigger conditions like offer_access_granted / product_completed.",
8582
8856
  {
8583
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8857
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8584
8858
  },
8585
8859
  async ({ locationId: locationId2 }) => {
8586
8860
  const loc = locationId2 ?? client.locationId;
@@ -8592,8 +8866,8 @@ ${text2}`);
8592
8866
  "list_membership_categories",
8593
8867
  "List all membership/course categories in a location. Categories group lessons inside a course/product. Use the returned ids with the category_completed / category_started trigger conditions. READ-ONLY.",
8594
8868
  {
8595
- limit: import_zod46.z.number().optional().describe("Max categories to return. Defaults to a large value (effectively all)."),
8596
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8869
+ limit: import_zod47.z.number().optional().describe("Max categories to return. Defaults to a large value (effectively all)."),
8870
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8597
8871
  },
8598
8872
  async ({ limit, locationId: locationId2 }) => {
8599
8873
  const loc = locationId2 ?? client.locationId;
@@ -8605,8 +8879,8 @@ ${text2}`);
8605
8879
  "list_membership_lessons",
8606
8880
  "List all membership/course lessons in a location. Use the returned ids with the lesson_completed / lesson_started trigger conditions. READ-ONLY.",
8607
8881
  {
8608
- limit: import_zod46.z.number().optional().describe("Max lessons to return. Defaults to a large value (effectively all)."),
8609
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8882
+ limit: import_zod47.z.number().optional().describe("Max lessons to return. Defaults to a large value (effectively all)."),
8883
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8610
8884
  },
8611
8885
  async ({ limit, locationId: locationId2 }) => {
8612
8886
  const loc = locationId2 ?? client.locationId;
@@ -8618,9 +8892,9 @@ ${text2}`);
8618
8892
  "create_course",
8619
8893
  "Create a membership course (a 'product') in a location. Creates the course shell with a title and description; add categories (create_membership_category) and lessons (create_membership_lesson) into it, and an offer (create_membership_offer) to grant access. Returns the new product, including its id. Requires Firebase auth.",
8620
8894
  {
8621
- title: import_zod46.z.string().describe("Course title."),
8622
- description: import_zod46.z.string().optional().describe("Course description. Defaults to empty."),
8623
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8895
+ title: import_zod47.z.string().describe("Course title."),
8896
+ description: import_zod47.z.string().optional().describe("Course description. Defaults to empty."),
8897
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8624
8898
  },
8625
8899
  async ({ title, description, locationId: locationId2 }) => {
8626
8900
  const loc = locationId2 ?? client.locationId;
@@ -8632,13 +8906,13 @@ ${text2}`);
8632
8906
  "create_membership_category",
8633
8907
  "Create a category (module/section) inside a membership course. Categories group lessons. Needs the productId of the course (from create_course or list_membership_offers). Returns the new category, including its id (use it as categoryId when creating lessons). Requires Firebase auth.",
8634
8908
  {
8635
- title: import_zod46.z.string().describe("Category title (e.g. 'Module 1')."),
8636
- productId: import_zod46.z.string().describe("The course/product id this category belongs to."),
8637
- description: import_zod46.z.string().optional().describe("Category description. Defaults to empty."),
8638
- visibility: import_zod46.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
8639
- sequenceNo: import_zod46.z.number().optional().describe("Display order within the course. Defaults to 0."),
8640
- dripDays: import_zod46.z.number().optional().describe("Days after enrollment before this category unlocks. Defaults to 0 (no drip)."),
8641
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8909
+ title: import_zod47.z.string().describe("Category title (e.g. 'Module 1')."),
8910
+ productId: import_zod47.z.string().describe("The course/product id this category belongs to."),
8911
+ description: import_zod47.z.string().optional().describe("Category description. Defaults to empty."),
8912
+ visibility: import_zod47.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
8913
+ sequenceNo: import_zod47.z.number().optional().describe("Display order within the course. Defaults to 0."),
8914
+ dripDays: import_zod47.z.number().optional().describe("Days after enrollment before this category unlocks. Defaults to 0 (no drip)."),
8915
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8642
8916
  },
8643
8917
  async ({ title, productId, description, visibility, sequenceNo, dripDays, locationId: locationId2 }) => {
8644
8918
  const loc = locationId2 ?? client.locationId;
@@ -8650,14 +8924,14 @@ ${text2}`);
8650
8924
  "create_membership_lesson",
8651
8925
  "Create a lesson (a 'post') inside a membership course category. Needs both the categoryId (from create_membership_category) and the productId of the course. Description is the lesson body as HTML. Returns the new lesson, including its id. Requires Firebase auth.",
8652
8926
  {
8653
- title: import_zod46.z.string().describe("Lesson title."),
8654
- categoryId: import_zod46.z.string().describe("The category id this lesson belongs to (from create_membership_category)."),
8655
- productId: import_zod46.z.string().describe("The course/product id this lesson belongs to."),
8656
- description: import_zod46.z.string().optional().describe("Lesson body as HTML. Defaults to empty."),
8657
- contentType: import_zod46.z.enum(["video", "audio", "text", "pdf", "assignment"]).optional().describe("Lesson content type. Defaults to 'video'."),
8658
- visibility: import_zod46.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
8659
- sequenceNo: import_zod46.z.number().optional().describe("Display order within the category. Defaults to 0."),
8660
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8927
+ title: import_zod47.z.string().describe("Lesson title."),
8928
+ categoryId: import_zod47.z.string().describe("The category id this lesson belongs to (from create_membership_category)."),
8929
+ productId: import_zod47.z.string().describe("The course/product id this lesson belongs to."),
8930
+ description: import_zod47.z.string().optional().describe("Lesson body as HTML. Defaults to empty."),
8931
+ contentType: import_zod47.z.enum(["video", "audio", "text", "pdf", "assignment"]).optional().describe("Lesson content type. Defaults to 'video'."),
8932
+ visibility: import_zod47.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
8933
+ sequenceNo: import_zod47.z.number().optional().describe("Display order within the category. Defaults to 0."),
8934
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8661
8935
  },
8662
8936
  async ({ title, categoryId, productId, description, contentType, visibility, sequenceNo, locationId: locationId2 }) => {
8663
8937
  const loc = locationId2 ?? client.locationId;
@@ -8669,12 +8943,12 @@ ${text2}`);
8669
8943
  "create_membership_offer",
8670
8944
  "Create a membership offer \u2014 the access grant that enrolls contacts into one or more courses/products. Link it to course product ids. Defaults to a free offer; for paid, set type to 'recurring' or 'one_time' with an amount. Returns the new offer, including its id (referenced by the offer_access_granted trigger). Requires Firebase auth.",
8671
8945
  {
8672
- title: import_zod46.z.string().describe("Offer title (shown at checkout / in the offer list)."),
8673
- productIds: import_zod46.z.array(import_zod46.z.string()).describe("Course/product ids this offer grants access to (from create_course or list_membership_offers)."),
8674
- type: import_zod46.z.enum(["free", "recurring", "one_time"]).optional().describe("Offer type. Defaults to 'free'."),
8675
- amount: import_zod46.z.number().optional().describe("Price for paid offers. Defaults to 0 (free)."),
8676
- currency: import_zod46.z.string().optional().describe("Currency code for paid offers. Defaults to 'USD'."),
8677
- locationId: import_zod46.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8946
+ title: import_zod47.z.string().describe("Offer title (shown at checkout / in the offer list)."),
8947
+ productIds: import_zod47.z.array(import_zod47.z.string()).describe("Course/product ids this offer grants access to (from create_course or list_membership_offers)."),
8948
+ type: import_zod47.z.enum(["free", "recurring", "one_time"]).optional().describe("Offer type. Defaults to 'free'."),
8949
+ amount: import_zod47.z.number().optional().describe("Price for paid offers. Defaults to 0 (free)."),
8950
+ currency: import_zod47.z.string().optional().describe("Currency code for paid offers. Defaults to 'USD'."),
8951
+ locationId: import_zod47.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
8678
8952
  },
8679
8953
  async ({ title, productIds, type, amount, currency, locationId: locationId2 }) => {
8680
8954
  const loc = locationId2 ?? client.locationId;
@@ -8733,60 +9007,60 @@ function buildOfferPayload(o) {
8733
9007
  }
8734
9008
 
8735
9009
  // src/tools/template-deployer.ts
8736
- var import_zod47 = require("zod");
8737
- var fs4 = __toESM(require("fs"));
8738
- var path4 = __toESM(require("path"));
9010
+ var import_zod48 = require("zod");
9011
+ var fs5 = __toESM(require("fs"));
9012
+ var path5 = __toESM(require("path"));
8739
9013
  function delay3(ms) {
8740
9014
  return new Promise((resolve5) => setTimeout(resolve5, ms));
8741
9015
  }
8742
- var TemplateSchema = import_zod47.z.object({
8743
- templateName: import_zod47.z.string(),
8744
- templateVersion: import_zod47.z.string().optional(),
8745
- description: import_zod47.z.string().optional().default(""),
8746
- questionnaire: import_zod47.z.array(import_zod47.z.object({
8747
- id: import_zod47.z.string(),
8748
- question: import_zod47.z.string(),
8749
- type: import_zod47.z.string(),
8750
- required: import_zod47.z.boolean().optional(),
8751
- placeholder: import_zod47.z.string().optional()
9016
+ var TemplateSchema = import_zod48.z.object({
9017
+ templateName: import_zod48.z.string(),
9018
+ templateVersion: import_zod48.z.string().optional(),
9019
+ description: import_zod48.z.string().optional().default(""),
9020
+ questionnaire: import_zod48.z.array(import_zod48.z.object({
9021
+ id: import_zod48.z.string(),
9022
+ question: import_zod48.z.string(),
9023
+ type: import_zod48.z.string(),
9024
+ required: import_zod48.z.boolean().optional(),
9025
+ placeholder: import_zod48.z.string().optional()
8752
9026
  })).optional().default([]),
8753
- location: import_zod47.z.record(import_zod47.z.unknown()).optional(),
8754
- tags: import_zod47.z.array(import_zod47.z.string()).optional(),
8755
- customFields: import_zod47.z.array(import_zod47.z.object({
8756
- name: import_zod47.z.string(),
8757
- dataType: import_zod47.z.string()
9027
+ location: import_zod48.z.record(import_zod48.z.unknown()).optional(),
9028
+ tags: import_zod48.z.array(import_zod48.z.string()).optional(),
9029
+ customFields: import_zod48.z.array(import_zod48.z.object({
9030
+ name: import_zod48.z.string(),
9031
+ dataType: import_zod48.z.string()
8758
9032
  })).optional(),
8759
- pipelines: import_zod47.z.array(import_zod47.z.object({
8760
- name: import_zod47.z.string(),
8761
- stages: import_zod47.z.array(import_zod47.z.object({ position: import_zod47.z.number(), name: import_zod47.z.string() }))
9033
+ pipelines: import_zod48.z.array(import_zod48.z.object({
9034
+ name: import_zod48.z.string(),
9035
+ stages: import_zod48.z.array(import_zod48.z.object({ position: import_zod48.z.number(), name: import_zod48.z.string() }))
8762
9036
  })).optional(),
8763
- workflows: import_zod47.z.array(import_zod47.z.object({
8764
- name: import_zod47.z.string(),
8765
- condition: import_zod47.z.string().optional(),
8766
- actions: import_zod47.z.array(import_zod47.z.record(import_zod47.z.unknown())).optional().default([])
9037
+ workflows: import_zod48.z.array(import_zod48.z.object({
9038
+ name: import_zod48.z.string(),
9039
+ condition: import_zod48.z.string().optional(),
9040
+ actions: import_zod48.z.array(import_zod48.z.record(import_zod48.z.unknown())).optional().default([])
8767
9041
  })).optional(),
8768
- calendars: import_zod47.z.array(import_zod47.z.object({
8769
- name: import_zod47.z.string(),
8770
- description: import_zod47.z.string().optional()
9042
+ calendars: import_zod48.z.array(import_zod48.z.object({
9043
+ name: import_zod48.z.string(),
9044
+ description: import_zod48.z.string().optional()
8771
9045
  })).optional()
8772
9046
  });
8773
9047
  function registerTemplateDeployerTools(server2, client) {
8774
9048
  const builderClient = WorkflowBuilderClient.fromEnv();
8775
- const templatesDir = path4.resolve(__dirname, "..", "templates");
9049
+ const templatesDir = path5.resolve(__dirname, "..", "templates");
8776
9050
  function validateTemplatePath(templateFile) {
8777
- const resolved = path4.resolve(templateFile);
8778
- const realPath = fs4.existsSync(resolved) ? fs4.realpathSync(resolved) : resolved;
9051
+ const resolved = path5.resolve(templateFile);
9052
+ const realPath = fs5.existsSync(resolved) ? fs5.realpathSync(resolved) : resolved;
8779
9053
  const allowedDirs = [
8780
9054
  templatesDir,
8781
- path4.resolve(process.cwd(), "templates"),
8782
- path4.resolve(__dirname, "..", "..", "templates")
8783
- ].map((d) => fs4.existsSync(d) ? fs4.realpathSync(d) : d);
8784
- const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path4.sep));
9055
+ path5.resolve(process.cwd(), "templates"),
9056
+ path5.resolve(__dirname, "..", "..", "templates")
9057
+ ].map((d) => fs5.existsSync(d) ? fs5.realpathSync(d) : d);
9058
+ const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path5.sep));
8785
9059
  if (!isAllowed) {
8786
9060
  throw new Error(`Template file must be inside a templates/ directory. Got: ${realPath}`);
8787
9061
  }
8788
- if (path4.relative(allowedDirs[0], realPath).startsWith("..")) {
8789
- const otherMatch = allowedDirs.slice(1).some((d) => !path4.relative(d, realPath).startsWith(".."));
9062
+ if (path5.relative(allowedDirs[0], realPath).startsWith("..")) {
9063
+ const otherMatch = allowedDirs.slice(1).some((d) => !path5.relative(d, realPath).startsWith(".."));
8790
9064
  if (!otherMatch) {
8791
9065
  throw new Error(`Template path escapes allowed directory.`);
8792
9066
  }
@@ -8801,19 +9075,19 @@ function registerTemplateDeployerTools(server2, client) {
8801
9075
  try {
8802
9076
  const dirs = [
8803
9077
  templatesDir,
8804
- path4.resolve(process.cwd(), "templates"),
8805
- path4.resolve(__dirname, "..", "..", "templates")
9078
+ path5.resolve(process.cwd(), "templates"),
9079
+ path5.resolve(__dirname, "..", "..", "templates")
8806
9080
  ];
8807
9081
  const templates = [];
8808
9082
  for (const dir of dirs) {
8809
- if (!fs4.existsSync(dir)) continue;
8810
- const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".json"));
9083
+ if (!fs5.existsSync(dir)) continue;
9084
+ const files = fs5.readdirSync(dir).filter((f) => f.endsWith(".json"));
8811
9085
  for (const file of files) {
8812
9086
  try {
8813
- const content = JSON.parse(fs4.readFileSync(path4.join(dir, file), "utf-8"));
9087
+ const content = JSON.parse(fs5.readFileSync(path5.join(dir, file), "utf-8"));
8814
9088
  templates.push({
8815
9089
  name: content.templateName || file,
8816
- file: path4.join(dir, file),
9090
+ file: path5.join(dir, file),
8817
9091
  description: content.description || ""
8818
9092
  });
8819
9093
  } catch {
@@ -8838,12 +9112,12 @@ function registerTemplateDeployerTools(server2, client) {
8838
9112
  "get_template_questionnaire",
8839
9113
  "Get the questionnaire for a specific template. Returns all the questions that need to be answered before deploying. Present these to the user one at a time in a conversational style.",
8840
9114
  {
8841
- templateFile: import_zod47.z.string().describe("Path to the template JSON file (from list_templates).")
9115
+ templateFile: import_zod48.z.string().describe("Path to the template JSON file (from list_templates).")
8842
9116
  },
8843
9117
  async ({ templateFile }) => {
8844
9118
  try {
8845
9119
  const safePath = validateTemplatePath(templateFile);
8846
- const content = JSON.parse(fs4.readFileSync(safePath, "utf-8"));
9120
+ const content = JSON.parse(fs5.readFileSync(safePath, "utf-8"));
8847
9121
  return {
8848
9122
  content: [
8849
9123
  {
@@ -8871,16 +9145,16 @@ function registerTemplateDeployerTools(server2, client) {
8871
9145
  "deploy_template",
8872
9146
  "Deploy a template to set up a GHL sub-account. Creates tags, custom fields, pipelines with stages, calendars, workflows, and forms based on the template and the user's questionnaire answers. This is the main setup automation tool.",
8873
9147
  {
8874
- templateFile: import_zod47.z.string().describe("Path to the template JSON file."),
8875
- answers: import_zod47.z.record(import_zod47.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
8876
- locationId: import_zod47.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
8877
- dryRun: import_zod47.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false.")
9148
+ templateFile: import_zod48.z.string().describe("Path to the template JSON file."),
9149
+ answers: import_zod48.z.record(import_zod48.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
9150
+ locationId: import_zod48.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
9151
+ dryRun: import_zod48.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false.")
8878
9152
  },
8879
9153
  async ({ templateFile, answers, locationId: locationId2, dryRun }) => {
8880
9154
  try {
8881
9155
  const locId = client.resolveLocationId(locationId2);
8882
9156
  const safePath = validateTemplatePath(templateFile);
8883
- const template = TemplateSchema.parse(JSON.parse(fs4.readFileSync(safePath, "utf-8")));
9157
+ const template = TemplateSchema.parse(JSON.parse(fs5.readFileSync(safePath, "utf-8")));
8884
9158
  const resolve5 = (text) => {
8885
9159
  if (typeof text !== "string") return text;
8886
9160
  let result = text;
@@ -9120,7 +9394,7 @@ ${errors.join("\n")}` : "\nNo errors!",
9120
9394
  }
9121
9395
 
9122
9396
  // src/tools/validators.ts
9123
- var import_zod48 = require("zod");
9397
+ var import_zod49 = require("zod");
9124
9398
  var ALL_CATEGORIES = ["pipeline", "stage", "custom_field", "user", "workflow", "form", "calendar", "survey"];
9125
9399
  var STANDARD_CONTACT_FIELDS = /* @__PURE__ */ new Set([
9126
9400
  "first_name",
@@ -9578,7 +9852,7 @@ function registerValidatorTools(server2, client, builderClient) {
9578
9852
  server2.tool(
9579
9853
  "validate_workflow",
9580
9854
  "Pre-flight ID validation for ONE deployed GHL workflow. Scans every trigger and action for references to pipelines, pipeline stages, custom fields, users, workflows, forms, calendars, and surveys; verifies each ID exists in the current location. Use BEFORE publish_workflow when a workflow was edited, or when a published workflow stops behaving. Catches the silent-failure bug where invalid IDs make GHL skip all subsequent actions. Never reports a false break \u2014 anything it cannot fully verify is marked 'unverified', not 'error'.",
9581
- { workflowId: import_zod48.z.string().describe("The workflow ID to validate.") },
9855
+ { workflowId: import_zod49.z.string().describe("The workflow ID to validate.") },
9582
9856
  async ({ workflowId }) => {
9583
9857
  try {
9584
9858
  const workflow = await builderClient.getWorkflow(workflowId);
@@ -9830,10 +10104,10 @@ function registerDiagnosticTools(server2, installedVersion, client, builderClien
9830
10104
  }
9831
10105
 
9832
10106
  // src/tools/snapshots.ts
9833
- var import_zod49 = require("zod");
9834
- var SnapshotSchema = import_zod49.z.object({ id: import_zod49.z.string(), name: import_zod49.z.string(), type: import_zod49.z.string() }).passthrough();
9835
- var SnapshotsResponseSchema = import_zod49.z.object({ snapshots: import_zod49.z.array(SnapshotSchema) }).passthrough();
9836
- var ShareLinkResponseSchema = import_zod49.z.object({ id: import_zod49.z.string(), shareLink: import_zod49.z.string() }).passthrough();
10107
+ var import_zod50 = require("zod");
10108
+ var SnapshotSchema = import_zod50.z.object({ id: import_zod50.z.string(), name: import_zod50.z.string(), type: import_zod50.z.string() }).passthrough();
10109
+ var SnapshotsResponseSchema = import_zod50.z.object({ snapshots: import_zod50.z.array(SnapshotSchema) }).passthrough();
10110
+ var ShareLinkResponseSchema = import_zod50.z.object({ id: import_zod50.z.string(), shareLink: import_zod50.z.string() }).passthrough();
9837
10111
  var SHARE_TYPES = [
9838
10112
  "link",
9839
10113
  "permanent_link",
@@ -9880,7 +10154,7 @@ function registerSnapshotTools(server2, client, registry2) {
9880
10154
  "list_snapshots",
9881
10155
  "List the agency's GHL snapshots (id, name, type) so you can pick the right one by name before applying it to a sub-account. Reads the agency/company-scoped key (not a sub-account PIT). companyId defaults to the active location's company; pass it explicitly to target a different company you have the agency key for. Read-only.",
9882
10156
  {
9883
- companyId: import_zod49.z.string().optional().describe(
10157
+ companyId: import_zod50.z.string().optional().describe(
9884
10158
  "Agency/company ID whose snapshots to list. Defaults to the active location's company. Must match the company your agency key is scoped to."
9885
10159
  )
9886
10160
  },
@@ -9905,11 +10179,11 @@ function registerSnapshotTools(server2, client, registry2) {
9905
10179
  "create_snapshot_share_link",
9906
10180
  "Create a shareable load link for one of the agency's snapshots (returns a gohighlevel.com/?share=... URL to import the snapshot into a sub-account). Uses the agency/company-scoped key. WARNING: this is NOT idempotent \u2014 each call mints a NEW link, and there is no API to list or revoke links (revoke in the GHL UI under the snapshot's share settings). Pick share_type deliberately. Use list_snapshots first to get the snapshot id.",
9907
10181
  {
9908
- snapshot_id: import_zod49.z.string().describe("The snapshot id to share (from list_snapshots)."),
9909
- share_type: import_zod49.z.enum(SHARE_TYPES).describe(
10182
+ snapshot_id: import_zod50.z.string().describe("The snapshot id to share (from list_snapshots)."),
10183
+ share_type: import_zod50.z.enum(SHARE_TYPES).describe(
9910
10184
  "Share link type. 'link' = standard share link; 'permanent_link' = non-expiring; 'agency_link' = share to agencies; 'location_link' = load into a sub-account/location; 'marketplace_link' = marketplace listing. No default \u2014 choose intentionally."
9911
10185
  ),
9912
- companyId: import_zod49.z.string().optional().describe(
10186
+ companyId: import_zod50.z.string().optional().describe(
9913
10187
  "Agency/company ID that owns the snapshot. Defaults to the active location's company. Must match the company your agency key is scoped to."
9914
10188
  )
9915
10189
  },
@@ -9939,17 +10213,17 @@ function registerSnapshotTools(server2, client, registry2) {
9939
10213
  }
9940
10214
 
9941
10215
  // src/tools/phone.ts
9942
- var import_zod50 = require("zod");
9943
- var PhoneNumberSchema = import_zod50.z.object({ sid: import_zod50.z.string(), value: import_zod50.z.string(), title: import_zod50.z.string().optional() }).passthrough();
9944
- var NumbersResponseSchema = import_zod50.z.object({ phoneNumbers: import_zod50.z.array(PhoneNumberSchema) }).passthrough();
9945
- var PoolsResponseSchema = import_zod50.z.object({ pools: import_zod50.z.array(import_zod50.z.object({}).passthrough()) }).passthrough();
10216
+ var import_zod51 = require("zod");
10217
+ var PhoneNumberSchema = import_zod51.z.object({ sid: import_zod51.z.string(), value: import_zod51.z.string(), title: import_zod51.z.string().optional() }).passthrough();
10218
+ var NumbersResponseSchema = import_zod51.z.object({ phoneNumbers: import_zod51.z.array(PhoneNumberSchema) }).passthrough();
10219
+ var PoolsResponseSchema = import_zod51.z.object({ pools: import_zod51.z.array(import_zod51.z.object({}).passthrough()) }).passthrough();
9946
10220
  function registerPhoneTools(server2, client) {
9947
10221
  safeTool(
9948
10222
  server2,
9949
10223
  "list_phone_numbers",
9950
10224
  "List the LC Phone numbers provisioned for a location (sid, number, label). Read-only. Use to verify or count purchased numbers (e.g. provisioning step 11). Number purchase is not exposed (billable write).",
9951
10225
  {
9952
- locationId: import_zod50.z.string().optional().describe("Defaults to the active location.")
10226
+ locationId: import_zod51.z.string().optional().describe("Defaults to the active location.")
9953
10227
  },
9954
10228
  async ({ locationId: locationId2 }) => {
9955
10229
  const loc = client.resolveLocationId(locationId2);
@@ -9967,7 +10241,7 @@ function registerPhoneTools(server2, client) {
9967
10241
  "list_number_pools",
9968
10242
  "List LC Phone number pools configured for a location. Read-only.",
9969
10243
  {
9970
- locationId: import_zod50.z.string().optional().describe("Defaults to the active location.")
10244
+ locationId: import_zod51.z.string().optional().describe("Defaults to the active location.")
9971
10245
  },
9972
10246
  async ({ locationId: locationId2 }) => {
9973
10247
  const loc = client.resolveLocationId(locationId2);
@@ -9979,10 +10253,10 @@ function registerPhoneTools(server2, client) {
9979
10253
  }
9980
10254
 
9981
10255
  // src/tools/account-health.ts
9982
- var import_zod51 = require("zod");
9983
- var MetaTotalSchema = import_zod51.z.object({ meta: import_zod51.z.object({ total: import_zod51.z.number() }).passthrough() }).passthrough();
9984
- var TotalSchema = import_zod51.z.object({ total: import_zod51.z.number() }).passthrough();
9985
- var NumbersSchema = import_zod51.z.object({ phoneNumbers: import_zod51.z.array(import_zod51.z.unknown()) }).passthrough();
10256
+ var import_zod52 = require("zod");
10257
+ var MetaTotalSchema = import_zod52.z.object({ meta: import_zod52.z.object({ total: import_zod52.z.number() }).passthrough() }).passthrough();
10258
+ var TotalSchema = import_zod52.z.object({ total: import_zod52.z.number() }).passthrough();
10259
+ var NumbersSchema = import_zod52.z.object({ phoneNumbers: import_zod52.z.array(import_zod52.z.unknown()) }).passthrough();
9986
10260
  var OPP_STATUSES = ["open", "won", "lost", "abandoned"];
9987
10261
  async function section(scope, fn) {
9988
10262
  try {
@@ -9997,8 +10271,8 @@ function registerAccountHealthTools(server2, client) {
9997
10271
  "get_account_health_summary",
9998
10272
  "Account-health summary for a location, composed from existing reads (GHL has no reporting API). Returns: total contacts + NEW contacts in the window; total opportunities + counts by status (open/won/lost/abandoned); total conversations; phone-number count. Every metric is explicitly labeled all_time vs window (with start/end) \u2014 windowed and all-time numbers are never conflated. Sections that can't be read return status:'unavailable' (never a misleading 0). Revenue and appointments are intentionally excluded (not reachable / too costly via the public API).",
9999
10273
  {
10000
- locationId: import_zod51.z.string().optional().describe("Defaults to the active location."),
10001
- windowDays: import_zod51.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
10274
+ locationId: import_zod52.z.string().optional().describe("Defaults to the active location."),
10275
+ windowDays: import_zod52.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
10002
10276
  },
10003
10277
  async ({ locationId: locationId2, windowDays }) => {
10004
10278
  const loc = client.resolveLocationId(locationId2);
@@ -10070,7 +10344,7 @@ function registerAccountHealthTools(server2, client) {
10070
10344
  }
10071
10345
 
10072
10346
  // src/tools/intake-to-build.ts
10073
- var import_zod54 = require("zod");
10347
+ var import_zod55 = require("zod");
10074
10348
 
10075
10349
  // src/intake-to-build/question-set.ts
10076
10350
  var QUESTION_SET_VERSION = "0.1";
@@ -10772,7 +11046,7 @@ function buildPlanFormData(fields, locationId2) {
10772
11046
  }
10773
11047
 
10774
11048
  // src/intake-to-build/brief.ts
10775
- var import_zod52 = require("zod");
11049
+ var import_zod53 = require("zod");
10776
11050
  var BRIEF_SCHEMA_VERSION = "0.1";
10777
11051
  var PRESETS = [
10778
11052
  "generic",
@@ -10782,67 +11056,67 @@ var PRESETS = [
10782
11056
  "ecom",
10783
11057
  "agency"
10784
11058
  ];
10785
- var presetSchema = import_zod52.z.enum(PRESETS);
11059
+ var presetSchema = import_zod53.z.enum(PRESETS);
10786
11060
  var BRIEF_SOURCES = ["agency_os", "business_os", "intake_form", "hybrid"];
10787
- var briefSourceSchema = import_zod52.z.enum(BRIEF_SOURCES);
10788
- var pricePointSchema = import_zod52.z.object({
10789
- name: import_zod52.z.string(),
10790
- price: import_zod52.z.string()
11061
+ var briefSourceSchema = import_zod53.z.enum(BRIEF_SOURCES);
11062
+ var pricePointSchema = import_zod53.z.object({
11063
+ name: import_zod53.z.string(),
11064
+ price: import_zod53.z.string()
10791
11065
  });
10792
- var briefSchema = import_zod52.z.object({
10793
- schemaVersion: import_zod52.z.string(),
10794
- briefId: import_zod52.z.string(),
11066
+ var briefSchema = import_zod53.z.object({
11067
+ schemaVersion: import_zod53.z.string(),
11068
+ briefId: import_zod53.z.string(),
10795
11069
  preset: presetSchema,
10796
11070
  briefSource: briefSourceSchema,
10797
11071
  /** Partner-OS deep structures (ICA / offer / brand-DNA), carried verbatim. */
10798
- extended: import_zod52.z.record(import_zod52.z.unknown()).optional(),
10799
- business: import_zod52.z.object({
10800
- name: import_zod52.z.string(),
10801
- type: import_zod52.z.string().optional(),
10802
- website: import_zod52.z.string().optional(),
10803
- location: import_zod52.z.string().optional(),
10804
- timezone: import_zod52.z.string().optional(),
11072
+ extended: import_zod53.z.record(import_zod53.z.unknown()).optional(),
11073
+ business: import_zod53.z.object({
11074
+ name: import_zod53.z.string(),
11075
+ type: import_zod53.z.string().optional(),
11076
+ website: import_zod53.z.string().optional(),
11077
+ location: import_zod53.z.string().optional(),
11078
+ timezone: import_zod53.z.string().optional(),
10805
11079
  // Ratified additions (atlas 2026-06-15). Enum-ish but kept as strings for
10806
11080
  // the same tolerance reason as business.type (don't reject valid briefs).
10807
- teamSize: import_zod52.z.string().optional(),
10808
- monthlyLeadVolume: import_zod52.z.string().optional(),
10809
- hours: import_zod52.z.string().optional()
11081
+ teamSize: import_zod53.z.string().optional(),
11082
+ monthlyLeadVolume: import_zod53.z.string().optional(),
11083
+ hours: import_zod53.z.string().optional()
10810
11084
  }).passthrough(),
10811
- offer: import_zod52.z.object({
10812
- summary: import_zod52.z.string().optional(),
11085
+ offer: import_zod53.z.object({
11086
+ summary: import_zod53.z.string().optional(),
10813
11087
  // Parsed best-effort; tolerate a raw string when parsing was not possible.
10814
- pricePoints: import_zod52.z.union([import_zod52.z.array(pricePointSchema), import_zod52.z.string()]).optional(),
10815
- leadMagnet: import_zod52.z.string().optional(),
10816
- avgDealValue: import_zod52.z.string().optional()
11088
+ pricePoints: import_zod53.z.union([import_zod53.z.array(pricePointSchema), import_zod53.z.string()]).optional(),
11089
+ leadMagnet: import_zod53.z.string().optional(),
11090
+ avgDealValue: import_zod53.z.string().optional()
10817
11091
  }).passthrough().optional(),
10818
- audience: import_zod52.z.object({
10819
- ideal: import_zod52.z.string().optional(),
10820
- painPoints: import_zod52.z.array(import_zod52.z.string()).optional(),
10821
- objections: import_zod52.z.array(import_zod52.z.string()).optional()
11092
+ audience: import_zod53.z.object({
11093
+ ideal: import_zod53.z.string().optional(),
11094
+ painPoints: import_zod53.z.array(import_zod53.z.string()).optional(),
11095
+ objections: import_zod53.z.array(import_zod53.z.string()).optional()
10822
11096
  }).passthrough().optional(),
10823
- goal: import_zod52.z.object({
11097
+ goal: import_zod53.z.object({
10824
11098
  // Kept as string: the form option labels are the canonical values, but
10825
11099
  // the contract sample shortens some (e.g. "high-touch"). See §7 note.
10826
- primary: import_zod52.z.string(),
10827
- salesStages: import_zod52.z.array(import_zod52.z.string()).optional(),
10828
- bookingNeeded: import_zod52.z.boolean().optional(),
10829
- followUpStyle: import_zod52.z.string().optional()
11100
+ primary: import_zod53.z.string(),
11101
+ salesStages: import_zod53.z.array(import_zod53.z.string()).optional(),
11102
+ bookingNeeded: import_zod53.z.boolean().optional(),
11103
+ followUpStyle: import_zod53.z.string().optional()
10830
11104
  }).passthrough(),
10831
- channels: import_zod52.z.object({
10832
- email: import_zod52.z.boolean().optional(),
10833
- sms: import_zod52.z.boolean().optional(),
10834
- a2pStatus: import_zod52.z.string().optional(),
10835
- payment: import_zod52.z.string().optional(),
10836
- calendarConnected: import_zod52.z.boolean().optional(),
10837
- social: import_zod52.z.array(import_zod52.z.string()).optional()
11105
+ channels: import_zod53.z.object({
11106
+ email: import_zod53.z.boolean().optional(),
11107
+ sms: import_zod53.z.boolean().optional(),
11108
+ a2pStatus: import_zod53.z.string().optional(),
11109
+ payment: import_zod53.z.string().optional(),
11110
+ calendarConnected: import_zod53.z.boolean().optional(),
11111
+ social: import_zod53.z.array(import_zod53.z.string()).optional()
10838
11112
  }).passthrough().optional(),
10839
- assets: import_zod52.z.object({
10840
- existingPipeline: import_zod52.z.string().optional(),
10841
- existingWorkflows: import_zod52.z.string().optional(),
10842
- brand: import_zod52.z.string().optional(),
10843
- notes: import_zod52.z.string().optional()
11113
+ assets: import_zod53.z.object({
11114
+ existingPipeline: import_zod53.z.string().optional(),
11115
+ existingWorkflows: import_zod53.z.string().optional(),
11116
+ brand: import_zod53.z.string().optional(),
11117
+ notes: import_zod53.z.string().optional()
10844
11118
  }).passthrough().optional(),
10845
- flags: import_zod52.z.array(import_zod52.z.string()).optional()
11119
+ flags: import_zod53.z.array(import_zod53.z.string()).optional()
10846
11120
  }).strict();
10847
11121
  function validateBrief(input) {
10848
11122
  const parsed = briefSchema.safeParse(input);
@@ -10934,9 +11208,9 @@ function presetForBusinessType(type) {
10934
11208
  return "generic";
10935
11209
  }
10936
11210
  }
10937
- function setPath(target, path7, value) {
11211
+ function setPath(target, path8, value) {
10938
11212
  if (value === void 0) return;
10939
- const parts = path7.split(".");
11213
+ const parts = path8.split(".");
10940
11214
  let node = target;
10941
11215
  for (let i = 0; i < parts.length - 1; i++) {
10942
11216
  const k = parts[i];
@@ -10998,7 +11272,7 @@ function normalizeSubmissionToBrief(opts) {
10998
11272
  }
10999
11273
 
11000
11274
  // src/intake-to-build/plan.ts
11001
- var import_zod53 = require("zod");
11275
+ var import_zod54 = require("zod");
11002
11276
  var REF_NAMESPACES = [
11003
11277
  "pipeline",
11004
11278
  "stage",
@@ -11015,22 +11289,22 @@ var REF_NAMESPACES = [
11015
11289
  "handoff"
11016
11290
  ];
11017
11291
  var REF_RE = new RegExp(`^(${REF_NAMESPACES.join("|")})\\.[a-z0-9]+(_[a-z0-9]+)*$`);
11018
- var refSchema = import_zod53.z.string().regex(REF_RE, "must be a <namespace>.<snake_case_slug> ref (no real GHL IDs)");
11292
+ var refSchema = import_zod54.z.string().regex(REF_RE, "must be a <namespace>.<snake_case_slug> ref (no real GHL IDs)");
11019
11293
  function nsRef(ns) {
11020
- return import_zod53.z.string().regex(new RegExp(`^${ns}\\.[a-z0-9]+(_[a-z0-9]+)*$`), `must be a ${ns}.* ref`);
11294
+ return import_zod54.z.string().regex(new RegExp(`^${ns}\\.[a-z0-9]+(_[a-z0-9]+)*$`), `must be a ${ns}.* ref`);
11021
11295
  }
11022
11296
  function refNamespace(ref) {
11023
11297
  return ref.split(".")[0];
11024
11298
  }
11025
- var stageSchema = import_zod53.z.object({
11299
+ var stageSchema = import_zod54.z.object({
11026
11300
  ref: nsRef("stage"),
11027
- name: import_zod53.z.string(),
11028
- position: import_zod53.z.number().int().nonnegative()
11301
+ name: import_zod54.z.string(),
11302
+ position: import_zod54.z.number().int().nonnegative()
11029
11303
  });
11030
- var pipelineSchema = import_zod53.z.object({
11304
+ var pipelineSchema = import_zod54.z.object({
11031
11305
  ref: nsRef("pipeline"),
11032
- name: import_zod53.z.string(),
11033
- stages: import_zod53.z.array(stageSchema).min(1)
11306
+ name: import_zod54.z.string(),
11307
+ stages: import_zod54.z.array(stageSchema).min(1)
11034
11308
  });
11035
11309
  var GHL_FIELD_DATATYPES = [
11036
11310
  "TEXT",
@@ -11047,21 +11321,21 @@ var GHL_FIELD_DATATYPES = [
11047
11321
  "FILE_UPLOAD",
11048
11322
  "SIGNATURE"
11049
11323
  ];
11050
- var customFieldSchema = import_zod53.z.object({
11324
+ var customFieldSchema = import_zod54.z.object({
11051
11325
  ref: nsRef("field"),
11052
- name: import_zod53.z.string(),
11053
- dataType: import_zod53.z.enum(GHL_FIELD_DATATYPES),
11054
- model: import_zod53.z.enum(["contact", "opportunity"]).optional(),
11055
- options: import_zod53.z.array(import_zod53.z.string()).optional()
11326
+ name: import_zod54.z.string(),
11327
+ dataType: import_zod54.z.enum(GHL_FIELD_DATATYPES),
11328
+ model: import_zod54.z.enum(["contact", "opportunity"]).optional(),
11329
+ options: import_zod54.z.array(import_zod54.z.string()).optional()
11056
11330
  });
11057
- var tagSchema = import_zod53.z.object({
11331
+ var tagSchema = import_zod54.z.object({
11058
11332
  ref: nsRef("tag"),
11059
- name: import_zod53.z.string()
11333
+ name: import_zod54.z.string()
11060
11334
  });
11061
- var customValueSchema = import_zod53.z.object({
11335
+ var customValueSchema = import_zod54.z.object({
11062
11336
  ref: nsRef("cv"),
11063
- name: import_zod53.z.string(),
11064
- value: import_zod53.z.string().optional(),
11337
+ name: import_zod54.z.string(),
11338
+ value: import_zod54.z.string().optional(),
11065
11339
  filledBy: refSchema.optional()
11066
11340
  });
11067
11341
  var CALENDAR_TYPES = [
@@ -11071,89 +11345,89 @@ var CALENDAR_TYPES = [
11071
11345
  "collective",
11072
11346
  "service_booking"
11073
11347
  ];
11074
- var openHoursBlockSchema = import_zod53.z.object({
11075
- daysOfTheWeek: import_zod53.z.array(import_zod53.z.number().int().min(0).max(6)),
11076
- hours: import_zod53.z.array(
11077
- import_zod53.z.object({
11078
- openHour: import_zod53.z.number().int().min(0).max(23),
11079
- openMinute: import_zod53.z.number().int().min(0).max(59),
11080
- closeHour: import_zod53.z.number().int().min(0).max(23),
11081
- closeMinute: import_zod53.z.number().int().min(0).max(59)
11348
+ var openHoursBlockSchema = import_zod54.z.object({
11349
+ daysOfTheWeek: import_zod54.z.array(import_zod54.z.number().int().min(0).max(6)),
11350
+ hours: import_zod54.z.array(
11351
+ import_zod54.z.object({
11352
+ openHour: import_zod54.z.number().int().min(0).max(23),
11353
+ openMinute: import_zod54.z.number().int().min(0).max(59),
11354
+ closeHour: import_zod54.z.number().int().min(0).max(23),
11355
+ closeMinute: import_zod54.z.number().int().min(0).max(59)
11082
11356
  })
11083
11357
  )
11084
11358
  });
11085
- var calendarSchema = import_zod53.z.object({
11359
+ var calendarSchema = import_zod54.z.object({
11086
11360
  ref: nsRef("calendar"),
11087
- name: import_zod53.z.string(),
11088
- calendarType: import_zod53.z.enum(CALENDAR_TYPES),
11089
- openHours: import_zod53.z.array(openHoursBlockSchema).optional(),
11090
- availabilityType: import_zod53.z.number().int().optional(),
11091
- requiresStaff: import_zod53.z.boolean().optional()
11361
+ name: import_zod54.z.string(),
11362
+ calendarType: import_zod54.z.enum(CALENDAR_TYPES),
11363
+ openHours: import_zod54.z.array(openHoursBlockSchema).optional(),
11364
+ availabilityType: import_zod54.z.number().int().optional(),
11365
+ requiresStaff: import_zod54.z.boolean().optional()
11092
11366
  });
11093
- var formFieldSchema = import_zod53.z.discriminatedUnion("type", [
11094
- import_zod53.z.object({
11095
- type: import_zod53.z.literal("standard"),
11096
- key: import_zod53.z.string(),
11097
- required: import_zod53.z.boolean().optional()
11367
+ var formFieldSchema = import_zod54.z.discriminatedUnion("type", [
11368
+ import_zod54.z.object({
11369
+ type: import_zod54.z.literal("standard"),
11370
+ key: import_zod54.z.string(),
11371
+ required: import_zod54.z.boolean().optional()
11098
11372
  }),
11099
- import_zod53.z.object({
11100
- type: import_zod53.z.literal("custom"),
11373
+ import_zod54.z.object({
11374
+ type: import_zod54.z.literal("custom"),
11101
11375
  fieldRef: nsRef("field"),
11102
- required: import_zod53.z.boolean().optional()
11376
+ required: import_zod54.z.boolean().optional()
11103
11377
  })
11104
11378
  ]);
11105
- var formSchema = import_zod53.z.object({
11379
+ var formSchema = import_zod54.z.object({
11106
11380
  ref: nsRef("form"),
11107
- name: import_zod53.z.string(),
11108
- fields: import_zod53.z.array(formFieldSchema)
11381
+ name: import_zod54.z.string(),
11382
+ fields: import_zod54.z.array(formFieldSchema)
11109
11383
  });
11110
- var pageSchema = import_zod53.z.object({
11384
+ var pageSchema = import_zod54.z.object({
11111
11385
  ref: nsRef("page"),
11112
- name: import_zod53.z.string(),
11113
- role: import_zod53.z.string().optional(),
11114
- outline: import_zod53.z.string().optional(),
11386
+ name: import_zod54.z.string(),
11387
+ role: import_zod54.z.string().optional(),
11388
+ outline: import_zod54.z.string().optional(),
11115
11389
  formRef: nsRef("form").optional(),
11116
11390
  calendarRef: nsRef("calendar").optional()
11117
11391
  });
11118
11392
  var FUNNEL_TARGETS = ["ghl", "external"];
11119
11393
  var FUNNEL_HOSTS = ["cloudflare", "vercel"];
11120
- var funnelSchema = import_zod53.z.object({
11394
+ var funnelSchema = import_zod54.z.object({
11121
11395
  ref: nsRef("funnel"),
11122
- name: import_zod53.z.string(),
11396
+ name: import_zod54.z.string(),
11123
11397
  // Where the funnel is built. "ghl" (default) = funnel + named steps in GHL.
11124
11398
  // "external" = the subscriber builds + hosts the site themselves (Cloudflare/
11125
11399
  // Vercel) and wires its form back to this GHL sub-account (POWER-USER path —
11126
11400
  // see blueprint-funnel-targets-spec.md §9). The executor does NOT build or
11127
11401
  // deploy an external funnel; it surfaces the GHL-side wiring info.
11128
- target: import_zod53.z.enum(FUNNEL_TARGETS).optional(),
11129
- host: import_zod53.z.enum(FUNNEL_HOSTS).optional(),
11402
+ target: import_zod54.z.enum(FUNNEL_TARGETS).optional(),
11403
+ host: import_zod54.z.enum(FUNNEL_HOSTS).optional(),
11130
11404
  // external only
11131
- domain: import_zod53.z.string().optional(),
11405
+ domain: import_zod54.z.string().optional(),
11132
11406
  // external only
11133
- pages: import_zod53.z.array(pageSchema)
11407
+ pages: import_zod54.z.array(pageSchema)
11134
11408
  });
11135
- var emailAssetSchema = import_zod53.z.object({
11409
+ var emailAssetSchema = import_zod54.z.object({
11136
11410
  ref: nsRef("email"),
11137
- name: import_zod53.z.string(),
11138
- subject: import_zod53.z.string().optional(),
11139
- bodyOutline: import_zod53.z.string().optional(),
11140
- body: import_zod53.z.string().optional(),
11141
- mergeTags: import_zod53.z.array(import_zod53.z.string()).optional()
11411
+ name: import_zod54.z.string(),
11412
+ subject: import_zod54.z.string().optional(),
11413
+ bodyOutline: import_zod54.z.string().optional(),
11414
+ body: import_zod54.z.string().optional(),
11415
+ mergeTags: import_zod54.z.array(import_zod54.z.string()).optional()
11142
11416
  });
11143
- var smsAssetSchema = import_zod53.z.object({
11417
+ var smsAssetSchema = import_zod54.z.object({
11144
11418
  ref: nsRef("sms"),
11145
- name: import_zod53.z.string(),
11146
- bodyOutline: import_zod53.z.string().optional(),
11147
- body: import_zod53.z.string().optional(),
11148
- mergeTags: import_zod53.z.array(import_zod53.z.string()).optional()
11419
+ name: import_zod54.z.string(),
11420
+ bodyOutline: import_zod54.z.string().optional(),
11421
+ body: import_zod54.z.string().optional(),
11422
+ mergeTags: import_zod54.z.array(import_zod54.z.string()).optional()
11149
11423
  });
11150
- var waitUnit = import_zod53.z.enum(["minutes", "hours", "days"]);
11424
+ var waitUnit = import_zod54.z.enum(["minutes", "hours", "days"]);
11151
11425
  var branchActionOptions = [
11152
- import_zod53.z.object({ type: import_zod53.z.literal("add_contact_tag"), tagRef: nsRef("tag") }),
11153
- import_zod53.z.object({ type: import_zod53.z.literal("remove_contact_tag"), tagRef: nsRef("tag") }),
11154
- import_zod53.z.object({ type: import_zod53.z.literal("send_email"), emailRef: nsRef("email") }),
11155
- import_zod53.z.object({ type: import_zod53.z.literal("send_sms"), smsRef: nsRef("sms") }),
11156
- import_zod53.z.object({ type: import_zod53.z.literal("wait"), value: import_zod53.z.number().positive(), unit: waitUnit }),
11426
+ import_zod54.z.object({ type: import_zod54.z.literal("add_contact_tag"), tagRef: nsRef("tag") }),
11427
+ import_zod54.z.object({ type: import_zod54.z.literal("remove_contact_tag"), tagRef: nsRef("tag") }),
11428
+ import_zod54.z.object({ type: import_zod54.z.literal("send_email"), emailRef: nsRef("email") }),
11429
+ import_zod54.z.object({ type: import_zod54.z.literal("send_sms"), smsRef: nsRef("sms") }),
11430
+ import_zod54.z.object({ type: import_zod54.z.literal("wait"), value: import_zod54.z.number().positive(), unit: waitUnit }),
11157
11431
  // Appointment-relative wait ("wait until N BEFORE the appointment").
11158
11432
  // Only works when the workflow has an appointment in context (i.e. an
11159
11433
  // `appointment` trigger) — enforced by validateBuildPlan. Expands to GHL's
@@ -11162,85 +11436,85 @@ var branchActionOptions = [
11162
11436
  // (GHL stores whole minutes). Only "before" is emitted today — that's the
11163
11437
  // shape we captured + proved; "after" (post-appointment follow-up) is
11164
11438
  // deferred until its shape is captured from a real workflow.
11165
- import_zod53.z.object({
11166
- type: import_zod53.z.literal("wait_appointment"),
11167
- value: import_zod53.z.number().int().positive(),
11439
+ import_zod54.z.object({
11440
+ type: import_zod54.z.literal("wait_appointment"),
11441
+ value: import_zod54.z.number().int().positive(),
11168
11442
  unit: waitUnit
11169
11443
  }),
11170
- import_zod53.z.object({
11171
- type: import_zod53.z.literal("internal_notification"),
11172
- to: import_zod53.z.string(),
11173
- title: import_zod53.z.string(),
11174
- body: import_zod53.z.string()
11444
+ import_zod54.z.object({
11445
+ type: import_zod54.z.literal("internal_notification"),
11446
+ to: import_zod54.z.string(),
11447
+ title: import_zod54.z.string(),
11448
+ body: import_zod54.z.string()
11175
11449
  }),
11176
- import_zod53.z.object({
11177
- type: import_zod53.z.literal("update_contact_field"),
11450
+ import_zod54.z.object({
11451
+ type: import_zod54.z.literal("update_contact_field"),
11178
11452
  fieldRef: nsRef("field"),
11179
- value: import_zod53.z.string()
11453
+ value: import_zod54.z.string()
11180
11454
  }),
11181
- import_zod53.z.object({ type: import_zod53.z.literal("add_notes"), body: import_zod53.z.string() }),
11182
- import_zod53.z.object({
11183
- type: import_zod53.z.literal("task_notification"),
11184
- title: import_zod53.z.string(),
11185
- body: import_zod53.z.string().optional(),
11186
- dueDate: import_zod53.z.string().optional(),
11187
- assignedTo: import_zod53.z.string().optional()
11455
+ import_zod54.z.object({ type: import_zod54.z.literal("add_notes"), body: import_zod54.z.string() }),
11456
+ import_zod54.z.object({
11457
+ type: import_zod54.z.literal("task_notification"),
11458
+ title: import_zod54.z.string(),
11459
+ body: import_zod54.z.string().optional(),
11460
+ dueDate: import_zod54.z.string().optional(),
11461
+ assignedTo: import_zod54.z.string().optional()
11188
11462
  }),
11189
- import_zod53.z.object({ type: import_zod53.z.literal("remove_from_workflow"), workflowRef: nsRef("workflow") }),
11190
- import_zod53.z.object({ type: import_zod53.z.literal("add_to_workflow"), workflowRef: nsRef("workflow") }),
11191
- import_zod53.z.object({
11192
- type: import_zod53.z.literal("create_opportunity"),
11463
+ import_zod54.z.object({ type: import_zod54.z.literal("remove_from_workflow"), workflowRef: nsRef("workflow") }),
11464
+ import_zod54.z.object({ type: import_zod54.z.literal("add_to_workflow"), workflowRef: nsRef("workflow") }),
11465
+ import_zod54.z.object({
11466
+ type: import_zod54.z.literal("create_opportunity"),
11193
11467
  pipelineRef: nsRef("pipeline"),
11194
11468
  stageRef: nsRef("stage"),
11195
11469
  // Opportunity name (merge fields allowed). Defaults to the contact's name.
11196
11470
  // Required by GHL's create node; without it the create silently no-ops.
11197
- name: import_zod53.z.string().optional(),
11471
+ name: import_zod54.z.string().optional(),
11198
11472
  // Opportunity monetary value (the deal/sale dollar amount). A string so it
11199
11473
  // can be a literal ("2500") OR a merge field ("{{contact.package_value}}").
11200
11474
  // Optional — omitted → GHL leaves the value unset. Shape captured from Lux
11201
11475
  // Bio "14. Package Sale". Lux models the lifecycle by pipeline STAGE, not GHL
11202
11476
  // won/lost status, so a "won" opp = move to the closing stage WITH this value.
11203
- value: import_zod53.z.string().optional()
11477
+ value: import_zod54.z.string().optional()
11204
11478
  }),
11205
- import_zod53.z.object({
11206
- type: import_zod53.z.literal("update_opportunity"),
11479
+ import_zod54.z.object({
11480
+ type: import_zod54.z.literal("update_opportunity"),
11207
11481
  pipelineRef: nsRef("pipeline"),
11208
11482
  stageRef: nsRef("stage"),
11209
11483
  // Opportunity monetary value (see create_opportunity.value). Optional.
11210
- value: import_zod53.z.string().optional()
11484
+ value: import_zod54.z.string().optional()
11211
11485
  }),
11212
- import_zod53.z.object({
11213
- type: import_zod53.z.literal("goal_event"),
11214
- goalCondition: import_zod53.z.string(),
11486
+ import_zod54.z.object({
11487
+ type: import_zod54.z.literal("goal_event"),
11488
+ goalCondition: import_zod54.z.string(),
11215
11489
  // GHL's GoalAction enum (extracted 2026-05-18): continue | wait | exit.
11216
- action: import_zod53.z.enum(["exit", "continue", "wait"]).optional()
11490
+ action: import_zod54.z.enum(["exit", "continue", "wait"]).optional()
11217
11491
  })
11218
11492
  ];
11219
- var branchActionSchema = import_zod53.z.discriminatedUnion("type", branchActionOptions);
11220
- var findOpportunitySchema = import_zod53.z.object({
11221
- type: import_zod53.z.literal("find_opportunity"),
11493
+ var branchActionSchema = import_zod54.z.discriminatedUnion("type", branchActionOptions);
11494
+ var findOpportunitySchema = import_zod54.z.object({
11495
+ type: import_zod54.z.literal("find_opportunity"),
11222
11496
  pipelineRef: nsRef("pipeline"),
11223
- found: import_zod53.z.array(branchActionSchema).default([]),
11224
- notFound: import_zod53.z.array(branchActionSchema).default([])
11497
+ found: import_zod54.z.array(branchActionSchema).default([]),
11498
+ notFound: import_zod54.z.array(branchActionSchema).default([])
11225
11499
  });
11226
- var actionSchema = import_zod53.z.discriminatedUnion("type", [...branchActionOptions, findOpportunitySchema]);
11500
+ var actionSchema = import_zod54.z.discriminatedUnion("type", [...branchActionOptions, findOpportunitySchema]);
11227
11501
  var APPOINTMENT_STATUSES = ["new", "confirmed", "showed", "noshow", "cancelled", "invalid"];
11228
- var triggerSchema = import_zod53.z.object({
11229
- type: import_zod53.z.string(),
11502
+ var triggerSchema = import_zod54.z.object({
11503
+ type: import_zod54.z.string(),
11230
11504
  formRef: nsRef("form").optional(),
11231
11505
  tagRef: nsRef("tag").optional(),
11232
11506
  calendarRef: nsRef("calendar").optional(),
11233
11507
  pipelineRef: nsRef("pipeline").optional(),
11234
11508
  stageRef: nsRef("stage").optional(),
11235
11509
  // Required for a native `appointment` trigger (the status it fires on).
11236
- appointmentStatus: import_zod53.z.enum(APPOINTMENT_STATUSES).optional()
11510
+ appointmentStatus: import_zod54.z.enum(APPOINTMENT_STATUSES).optional()
11237
11511
  });
11238
- var workflowSchema = import_zod53.z.object({
11512
+ var workflowSchema = import_zod54.z.object({
11239
11513
  ref: nsRef("workflow"),
11240
- name: import_zod53.z.string(),
11514
+ name: import_zod54.z.string(),
11241
11515
  trigger: triggerSchema.optional(),
11242
- stopOnResponse: import_zod53.z.boolean().optional(),
11243
- actions: import_zod53.z.array(actionSchema).max(40)
11516
+ stopOnResponse: import_zod54.z.boolean().optional(),
11517
+ actions: import_zod54.z.array(actionSchema).max(40)
11244
11518
  // house rule: <=40 actions/workflow
11245
11519
  });
11246
11520
  var HANDOFF_OWNER_LEGACY = {
@@ -11248,35 +11522,35 @@ var HANDOFF_OWNER_LEGACY = {
11248
11522
  "JERRY-EXT": "OPERATOR-EXT",
11249
11523
  "SASHA": "TEAM"
11250
11524
  };
11251
- var handoffSchema = import_zod53.z.object({
11525
+ var handoffSchema = import_zod54.z.object({
11252
11526
  ref: nsRef("handoff"),
11253
- owner: import_zod53.z.enum(["OPERATOR-UI", "OPERATOR-EXT", "TEAM", "JERRY-UI", "JERRY-EXT", "SASHA"]).transform((o) => HANDOFF_OWNER_LEGACY[o] ?? o),
11254
- title: import_zod53.z.string(),
11255
- trigger: import_zod53.z.string().optional(),
11256
- instruction: import_zod53.z.string(),
11527
+ owner: import_zod54.z.enum(["OPERATOR-UI", "OPERATOR-EXT", "TEAM", "JERRY-UI", "JERRY-EXT", "SASHA"]).transform((o) => HANDOFF_OWNER_LEGACY[o] ?? o),
11528
+ title: import_zod54.z.string(),
11529
+ trigger: import_zod54.z.string().optional(),
11530
+ instruction: import_zod54.z.string(),
11257
11531
  produces: refSchema.nullable().optional(),
11258
- successCheck: import_zod53.z.string(),
11259
- blocks: import_zod53.z.array(import_zod53.z.string()).optional()
11532
+ successCheck: import_zod54.z.string(),
11533
+ blocks: import_zod54.z.array(import_zod54.z.string()).optional()
11260
11534
  });
11261
- var buildPlanSchema = import_zod53.z.object({
11262
- schemaVersion: import_zod53.z.string(),
11263
- planId: import_zod53.z.string(),
11264
- briefId: import_zod53.z.string(),
11265
- preset: import_zod53.z.string(),
11266
- summary: import_zod53.z.string().optional(),
11267
- pipelines: import_zod53.z.array(pipelineSchema).optional(),
11268
- customFields: import_zod53.z.array(customFieldSchema).optional(),
11269
- tags: import_zod53.z.array(tagSchema).optional(),
11270
- customValues: import_zod53.z.array(customValueSchema).optional(),
11271
- calendars: import_zod53.z.array(calendarSchema).optional(),
11272
- forms: import_zod53.z.array(formSchema).optional(),
11273
- funnels: import_zod53.z.array(funnelSchema).optional(),
11274
- emails: import_zod53.z.array(emailAssetSchema).optional(),
11275
- sms: import_zod53.z.array(smsAssetSchema).optional(),
11276
- workflows: import_zod53.z.array(workflowSchema).optional(),
11277
- handoffs: import_zod53.z.array(handoffSchema).optional(),
11278
- buildOrder: import_zod53.z.array(import_zod53.z.string()).optional(),
11279
- idMap: import_zod53.z.record(import_zod53.z.string()).optional()
11535
+ var buildPlanSchema = import_zod54.z.object({
11536
+ schemaVersion: import_zod54.z.string(),
11537
+ planId: import_zod54.z.string(),
11538
+ briefId: import_zod54.z.string(),
11539
+ preset: import_zod54.z.string(),
11540
+ summary: import_zod54.z.string().optional(),
11541
+ pipelines: import_zod54.z.array(pipelineSchema).optional(),
11542
+ customFields: import_zod54.z.array(customFieldSchema).optional(),
11543
+ tags: import_zod54.z.array(tagSchema).optional(),
11544
+ customValues: import_zod54.z.array(customValueSchema).optional(),
11545
+ calendars: import_zod54.z.array(calendarSchema).optional(),
11546
+ forms: import_zod54.z.array(formSchema).optional(),
11547
+ funnels: import_zod54.z.array(funnelSchema).optional(),
11548
+ emails: import_zod54.z.array(emailAssetSchema).optional(),
11549
+ sms: import_zod54.z.array(smsAssetSchema).optional(),
11550
+ workflows: import_zod54.z.array(workflowSchema).optional(),
11551
+ handoffs: import_zod54.z.array(handoffSchema).optional(),
11552
+ buildOrder: import_zod54.z.array(import_zod54.z.string()).optional(),
11553
+ idMap: import_zod54.z.record(import_zod54.z.string()).optional()
11280
11554
  }).strict();
11281
11555
  function collectDefinedRefs(plan) {
11282
11556
  const refs = /* @__PURE__ */ new Map();
@@ -12410,7 +12684,7 @@ function buildExternalWiring(plan, idMap, locationId2) {
12410
12684
 
12411
12685
  // src/intake-to-build/execute.ts
12412
12686
  var norm2 = (s) => s.trim().toLowerCase();
12413
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
12687
+ var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
12414
12688
  function slugifyName(s) {
12415
12689
  return s.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("-");
12416
12690
  }
@@ -12435,7 +12709,7 @@ async function executeBackbone(plan, deps, opts = {}) {
12435
12709
  const fresh = (await read()).filter((o) => norm2(o.name) === norm2(name) && !beforeIds.has(o.id));
12436
12710
  if (fresh.length === 1) return fresh[0];
12437
12711
  if (fresh.length > 1) return void 0;
12438
- if (attempt < retries) await sleep2(backoff * attempt);
12712
+ if (attempt < retries) await sleep3(backoff * attempt);
12439
12713
  }
12440
12714
  return void 0;
12441
12715
  }
@@ -12861,16 +13135,16 @@ function msg(e) {
12861
13135
  }
12862
13136
 
12863
13137
  // src/tools/intake-to-build.ts
12864
- var customFieldItemSchema = import_zod54.z.object({
12865
- id: import_zod54.z.string(),
12866
- name: import_zod54.z.string(),
12867
- fieldKey: import_zod54.z.string(),
12868
- dataType: import_zod54.z.string(),
12869
- model: import_zod54.z.string().optional(),
12870
- parentId: import_zod54.z.string().optional(),
12871
- position: import_zod54.z.number().optional(),
12872
- dateAdded: import_zod54.z.string().optional(),
12873
- picklistOptions: import_zod54.z.array(import_zod54.z.string()).optional()
13138
+ var customFieldItemSchema = import_zod55.z.object({
13139
+ id: import_zod55.z.string(),
13140
+ name: import_zod55.z.string(),
13141
+ fieldKey: import_zod55.z.string(),
13142
+ dataType: import_zod55.z.string(),
13143
+ model: import_zod55.z.string().optional(),
13144
+ parentId: import_zod55.z.string().optional(),
13145
+ position: import_zod55.z.number().optional(),
13146
+ dateAdded: import_zod55.z.string().optional(),
13147
+ picklistOptions: import_zod55.z.array(import_zod55.z.string()).optional()
12874
13148
  }).passthrough();
12875
13149
  function parseCustomFields(raw) {
12876
13150
  const obj = raw && typeof raw === "object" ? raw : {};
@@ -12917,7 +13191,7 @@ function findRecordForQuestion(q, records) {
12917
13191
  const wantName = intakeFieldName(q.label).toLowerCase();
12918
13192
  return records.find((r) => r.name.toLowerCase() === wantName);
12919
13193
  }
12920
- var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
13194
+ var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
12921
13195
  function isFormNotYetPropagated(error) {
12922
13196
  const msg2 = error instanceof Error ? error.message : String(error);
12923
13197
  return /does not exist or is deleted/i.test(msg2);
@@ -13022,15 +13296,15 @@ function extractFunnelId(result) {
13022
13296
  return void 0;
13023
13297
  }
13024
13298
  function makeExecuteDeps(client, builderClient, locationId2) {
13025
- const pipelineApi = async (method, path7, body) => {
13299
+ const pipelineApi = async (method, path8, body) => {
13026
13300
  const headers = await builderClient.buildHeaders();
13027
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path7}`;
13301
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path8}`;
13028
13302
  const options = { method, headers };
13029
13303
  if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
13030
13304
  const response = await fetch(url, options);
13031
13305
  if (!response.ok) {
13032
13306
  const text2 = await response.text();
13033
- throw new Error(`Pipeline API ${response.status}: ${method} ${path7}
13307
+ throw new Error(`Pipeline API ${response.status}: ${method} ${path8}
13034
13308
  ${text2.slice(0, 300)}`);
13035
13309
  }
13036
13310
  const text = await response.text();
@@ -13041,17 +13315,17 @@ ${text2.slice(0, 300)}`);
13041
13315
  return JSON.parse(text.replace(/[\x00-\x1F\x7F]/g, ""));
13042
13316
  }
13043
13317
  };
13044
- const funnelApi = async (method, path7, body) => {
13318
+ const funnelApi = async (method, path8, body) => {
13045
13319
  const headers = await builderClient.buildHeaders();
13046
13320
  headers.Origin = "https://app.gohighlevel.com";
13047
13321
  headers.Referer = "https://app.gohighlevel.com/";
13048
- const url = `https://backend.leadconnectorhq.com/funnels${path7}`;
13322
+ const url = `https://backend.leadconnectorhq.com/funnels${path8}`;
13049
13323
  const options = { method, headers };
13050
13324
  if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
13051
13325
  const response = await fetch(url, options);
13052
13326
  if (!response.ok) {
13053
13327
  const text2 = await response.text();
13054
- throw new Error(`Funnel API ${response.status}: ${method} ${path7}
13328
+ throw new Error(`Funnel API ${response.status}: ${method} ${path8}
13055
13329
  ${text2.slice(0, 300)}`);
13056
13330
  }
13057
13331
  const text = await response.text();
@@ -13137,7 +13411,7 @@ ${text2.slice(0, 300)}`);
13137
13411
  break;
13138
13412
  } catch (saveErr) {
13139
13413
  if (isFormNotYetPropagated(saveErr) && attempt < 6) {
13140
- await sleep3(700 * attempt);
13414
+ await sleep4(700 * attempt);
13141
13415
  continue;
13142
13416
  }
13143
13417
  throw saveErr;
@@ -13148,7 +13422,7 @@ ${text2.slice(0, 300)}`);
13148
13422
  const verify = await formApiRequest(builderClient, "GET", `/${formId}?locationId=${locationId2}`);
13149
13423
  persisted = countFormFields(verify);
13150
13424
  if (persisted > 0) break;
13151
- if (attempt < 6) await sleep3(700 * attempt);
13425
+ if (attempt < 6) await sleep4(700 * attempt);
13152
13426
  }
13153
13427
  if (persisted === 0) {
13154
13428
  throw new Error(`form "${name}" was created (${formId}) but no fields persisted after save (read-after-write); not binding`);
@@ -13279,7 +13553,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13279
13553
  "validate_brief",
13280
13554
  "Validate an Intake-to-Build Brief object against the \xA74 schema. Returns {valid, errors}. Use to check a normalized brief before handing it to the plan-generation skill.",
13281
13555
  {
13282
- brief: import_zod54.z.record(import_zod54.z.unknown()).describe("The Brief object to validate.")
13556
+ brief: import_zod55.z.record(import_zod55.z.unknown()).describe("The Brief object to validate.")
13283
13557
  },
13284
13558
  async ({ brief }) => validateBrief(brief)
13285
13559
  );
@@ -13288,7 +13562,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13288
13562
  "validate_build_plan",
13289
13563
  "Validate an Intake-to-Build Build Plan against the \xA75 schema AND check ref-integrity: every symbolic ref (pipeline.*, stage.*, tag.*, ...) must resolve to a defined object of the right type, and refs must be unique. Dead references are reported as errors here, before any build runs \u2014 the structural guard against 'an invalid ID silently kills downstream actions'. Returns {valid, errors, warnings, referencesScanned}.",
13290
13564
  {
13291
- plan: import_zod54.z.record(import_zod54.z.unknown()).describe("The Build Plan object to validate.")
13565
+ plan: import_zod55.z.record(import_zod55.z.unknown()).describe("The Build Plan object to validate.")
13292
13566
  },
13293
13567
  async ({ plan }) => validateBuildPlan(plan)
13294
13568
  );
@@ -13296,12 +13570,12 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13296
13570
  "apply_build_plan",
13297
13571
  `Take an APPROVED Blueprint \xA75 build plan + the CURRENT confirmed sub-account and build it. mode:"dry_run" (default) writes NOTHING \u2014 it resolves refs, expands each workflow's logical actions to native GHL JSON, runs the NEVER-CLOBBER existing-asset scan, and returns a two-part report. Run it FIRST. mode:"execute" performs LIVE writes for the CRM backbone (pipelines+stages, custom fields, tags, custom values), calendars, AND forms: never clobbers (same-named objects are bound to their existing id, never modified), verifies each create by read-back before resolving its ref, halts on the first failure returning the partial idMap, and is idempotent (re-run = no-op). Staff-requiring calendars (round_robin etc.) auto-build only when you are the sole account user (auto-assigns you as the team member); with 0 or 2+ users they're surfaced as a manual step, not auto-staffed to a guess. Forms build with their standard + custom fields (custom fieldRefs resolve to the real fields created earlier in the run). Funnels: a GHL funnel (target:"ghl", default) builds structurally (funnel + named steps; page content/HTML is a manual step \u2014 plans carry outlines); a funnel with target:"external" is NOT built or deployed here \u2014 the user builds + hosts the site themselves (Cloudflare/Vercel) and wires its form back to this GHL sub-account (surfaced as a manual wiring step). Workflows build as DRAFT with all their logical actions expanded to native GHL JSON (incl. opportunity create/move steps, re-enabled v3.41.0) and chained; a contact_tag trigger is built automatically, other trigger types are surfaced as a manual step; the operator reviews + publishes. Always confirms the active location and validates the plan before any write.`,
13298
13572
  {
13299
- plan: import_zod54.z.record(import_zod54.z.unknown()).describe("The approved \xA75 Build Plan object."),
13300
- mode: import_zod54.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes (not yet enabled)."),
13301
- locationId: import_zod54.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first)."),
13302
- metHandoffs: import_zod54.z.array(import_zod54.z.string()).optional().describe('Handoff refs the operator has already satisfied (e.g. ["handoff.a2p"]) \u2014 lifts their gate so dependent workflows are not held DRAFT.'),
13303
- publishWorkflows: import_zod54.z.boolean().optional().describe("If true, ungated workflows would be published instead of left DRAFT. Default false (DRAFT)."),
13304
- onConflict: import_zod54.z.enum(["skip", "abort"]).optional().describe("skip (default) = bind same-named existing objects and continue. abort = report conflicts as a halt.")
13573
+ plan: import_zod55.z.record(import_zod55.z.unknown()).describe("The approved \xA75 Build Plan object."),
13574
+ mode: import_zod55.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes (not yet enabled)."),
13575
+ locationId: import_zod55.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first)."),
13576
+ metHandoffs: import_zod55.z.array(import_zod55.z.string()).optional().describe('Handoff refs the operator has already satisfied (e.g. ["handoff.a2p"]) \u2014 lifts their gate so dependent workflows are not held DRAFT.'),
13577
+ publishWorkflows: import_zod55.z.boolean().optional().describe("If true, ungated workflows would be published instead of left DRAFT. Default false (DRAFT)."),
13578
+ onConflict: import_zod55.z.enum(["skip", "abort"]).optional().describe("skip (default) = bind same-named existing objects and continue. abort = report conflicts as a halt.")
13305
13579
  },
13306
13580
  async ({ plan, mode, locationId: locationId2, metHandoffs, publishWorkflows, onConflict }) => {
13307
13581
  try {
@@ -13490,7 +13764,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13490
13764
  resolved.set(q.key, rec);
13491
13765
  }
13492
13766
  if (!missing) break;
13493
- if (attempt < 6) await sleep3(700 * attempt);
13767
+ if (attempt < 6) await sleep4(700 * attempt);
13494
13768
  }
13495
13769
  if (missing) {
13496
13770
  throw new Error(
@@ -13504,9 +13778,9 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13504
13778
  "install_intake_form",
13505
13779
  "Install the canonical Intake-to-Build client-intake form into the CURRENT GHL location (whatever get_current_location returns). Account-agnostic, zero hardcoded IDs. Creates any missing intake custom fields (idempotent \u2014 reused on re-run), then builds the form with the proven GHL form-builder field shapes and verifies it. Returns {formId, fieldMap} \u2014 keep fieldMap; normalize_submission_to_brief uses it. Pass dryRun:true to preview what would be created without writing. Pass formId to update an existing intake form in place instead of creating a new one.",
13506
13780
  {
13507
- dryRun: import_zod54.z.boolean().optional().describe("Preview the fields/form that would be created without writing anything."),
13508
- formId: import_zod54.z.string().optional().describe("Update this existing form in place instead of creating a new one."),
13509
- formName: import_zod54.z.string().optional().describe(`Form name. Defaults to "${INTAKE_FORM_NAME}".`)
13781
+ dryRun: import_zod55.z.boolean().optional().describe("Preview the fields/form that would be created without writing anything."),
13782
+ formId: import_zod55.z.string().optional().describe("Update this existing form in place instead of creating a new one."),
13783
+ formName: import_zod55.z.string().optional().describe(`Form name. Defaults to "${INTAKE_FORM_NAME}".`)
13510
13784
  },
13511
13785
  async ({ dryRun, formId, formName }) => {
13512
13786
  try {
@@ -13556,7 +13830,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13556
13830
  break;
13557
13831
  } catch (saveErr) {
13558
13832
  if (justCreated && isFormNotYetPropagated(saveErr) && attempt < maxSaveAttempts) {
13559
- await sleep3(700 * attempt);
13833
+ await sleep4(700 * attempt);
13560
13834
  continue;
13561
13835
  }
13562
13836
  throw saveErr;
@@ -13568,7 +13842,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13568
13842
  const verify = await formApiRequest(bc, "GET", `/${resolvedFormId}?locationId=${locationId2}`);
13569
13843
  persistedCount = countFormFields(verify);
13570
13844
  if (persistedCount > 0) break;
13571
- if (attempt < 6) await sleep3(700 * attempt);
13845
+ if (attempt < 6) await sleep4(700 * attempt);
13572
13846
  }
13573
13847
  const fieldMap = {};
13574
13848
  for (const [key, rec] of resolved) fieldMap[key] = rec.id;
@@ -13594,10 +13868,10 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13594
13868
  "normalize_submission_to_brief",
13595
13869
  'Read an intake form submission and normalize it into a \xA74 Brief (briefSource:"intake_form"). Pass the formId; by default the most recent submission is used (or pass submissionId). The intakeKey->customFieldId map is taken from fieldMap if provided (the install_intake_form output, most robust), otherwise reconstructed from the live form. Returns {brief, validation, submissionId} \u2014 validation flags any missing required fields (e.g. an incomplete submission).',
13596
13870
  {
13597
- formId: import_zod54.z.string().describe("The intake form ID (from install_intake_form)."),
13598
- submissionId: import_zod54.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
13599
- fieldMap: import_zod54.z.record(import_zod54.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
13600
- preset: import_zod54.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
13871
+ formId: import_zod55.z.string().describe("The intake form ID (from install_intake_form)."),
13872
+ submissionId: import_zod55.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
13873
+ fieldMap: import_zod55.z.record(import_zod55.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
13874
+ preset: import_zod55.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
13601
13875
  },
13602
13876
  async ({ formId, submissionId, fieldMap, preset }) => {
13603
13877
  try {
@@ -13626,7 +13900,7 @@ function registerIntakeToBuildTools(server2, client, builderClient) {
13626
13900
  const formFull = await formApiRequest(bc, "GET", `/${formId}?locationId=${locationId2}`);
13627
13901
  resolvedMap = buildFieldMapFromFormFields(extractFormFields(formFull));
13628
13902
  }
13629
- const presetSchema2 = import_zod54.z.enum(["generic", "med_spa", "clinic_launch_a2p", "coach", "ecom", "agency"]).optional();
13903
+ const presetSchema2 = import_zod55.z.enum(["generic", "med_spa", "clinic_launch_a2p", "coach", "ecom", "agency"]).optional();
13630
13904
  const presetParsed = presetSchema2.safeParse(preset);
13631
13905
  const brief = normalizeSubmissionToBrief({
13632
13906
  others,
@@ -13859,8 +14133,8 @@ function registerMetaTools(server2, installedVersion) {
13859
14133
 
13860
14134
  // src/cli.ts
13861
14135
  var import_node_util = require("node:util");
13862
- var fs5 = __toESM(require("fs"));
13863
- var path5 = __toESM(require("path"));
14136
+ var fs6 = __toESM(require("fs"));
14137
+ var path6 = __toESM(require("path"));
13864
14138
  var import_crypto2 = require("crypto");
13865
14139
  var EXIT_OK = 0;
13866
14140
  var EXIT_USAGE = 2;
@@ -13899,9 +14173,9 @@ function errLine(msg2) {
13899
14173
  function preflightWritable() {
13900
14174
  try {
13901
14175
  const dir = ensureAppDataDir();
13902
- const probe = path5.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
13903
- fs5.writeFileSync(probe, "ok");
13904
- fs5.unlinkSync(probe);
14176
+ const probe = path6.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
14177
+ fs6.writeFileSync(probe, "ok");
14178
+ fs6.unlinkSync(probe);
13905
14179
  return true;
13906
14180
  } catch (error) {
13907
14181
  errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
@@ -14151,7 +14425,7 @@ var bundledPkg = require_package();
14151
14425
  var pkg = (() => {
14152
14426
  try {
14153
14427
  const onDisk = JSON.parse(
14154
- fs6.readFileSync(path6.resolve(__dirname, "..", "package.json"), "utf8")
14428
+ fs7.readFileSync(path7.resolve(__dirname, "..", "package.json"), "utf8")
14155
14429
  );
14156
14430
  if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
14157
14431
  return { version: onDisk.version };
@@ -14163,7 +14437,7 @@ var pkg = (() => {
14163
14437
  dotenv2.config();
14164
14438
  {
14165
14439
  const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
14166
- if (configDirOverride && !path6.isAbsolute(configDirOverride)) {
14440
+ if (configDirOverride && !path7.isAbsolute(configDirOverride)) {
14167
14441
  process.stderr.write(
14168
14442
  `[ghl-mcp] GHL_MCP_CONFIG_DIR must be an absolute path (got "${configDirOverride}"). Use e.g. /data/ghl-mcp in a container, with a volume mounted at /data.
14169
14443
  `
@@ -14176,20 +14450,20 @@ process.on("unhandledRejection", (reason) => {
14176
14450
  `);
14177
14451
  });
14178
14452
  function hardenSecretFilePerms() {
14179
- const repoDir = path6.resolve(__dirname, "..");
14453
+ const repoDir = path7.resolve(__dirname, "..");
14180
14454
  const candidates = [
14181
- { file: path6.join(repoDir, "start-mcp.sh"), mode: 448 },
14455
+ { file: path7.join(repoDir, "start-mcp.sh"), mode: 448 },
14182
14456
  // Legacy registry location (pre-migration); new location lives in app-data.
14183
- { file: path6.join(repoDir, ".ghl-tokens.json"), mode: 384 },
14457
+ { file: path7.join(repoDir, ".ghl-tokens.json"), mode: 384 },
14184
14458
  { file: tokenRegistryPath(), mode: 384 }
14185
14459
  ];
14186
14460
  for (const { file, mode } of candidates) {
14187
14461
  let current;
14188
14462
  try {
14189
- if (!fs6.existsSync(file)) continue;
14190
- current = fs6.statSync(file).mode & 511;
14463
+ if (!fs7.existsSync(file)) continue;
14464
+ current = fs7.statSync(file).mode & 511;
14191
14465
  if (current !== mode) {
14192
- fs6.chmodSync(file, mode);
14466
+ fs7.chmodSync(file, mode);
14193
14467
  }
14194
14468
  } catch (error) {
14195
14469
  const message = error instanceof Error ? error.message : String(error);
@@ -14363,6 +14637,7 @@ async function resolveAccessAndRegister() {
14363
14637
  registerAllTools(server, client, registry, pkg.version);
14364
14638
  registerEnableWorkflowBuilderTool(server);
14365
14639
  registerFirebaseCaptureScriptTool(server);
14640
+ registerInteractiveCaptureTool(server);
14366
14641
  if (fileCreds && !process.env.GHL_API_KEY) {
14367
14642
  process.stderr.write(`[ghl-mcp] Loaded credentials from ${credentialsPath()}
14368
14643
  `);