@elitedcs/ghl-mcp 3.54.0 → 3.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +8 -6
- package/dist/index.js +307 -145
- package/package.json +4 -3
- package/skills/blueprint/README.md +27 -0
- package/skills/blueprint/SKILL.md +149 -0
- package/skills/blueprint/examples/medspa-approval-view.md +92 -0
- package/skills/blueprint/examples/medspa-brief.json +52 -0
- package/skills/blueprint/examples/medspa-build-plan.json +265 -0
- package/skills/blueprint/examples/medspa-dry-run-report.md +67 -0
- package/skills/blueprint/examples/sample-approval-view.md +82 -0
- package/skills/blueprint/examples/sample-brief.json +13 -0
- package/skills/blueprint/examples/sample-build-plan.json +227 -0
- package/skills/blueprint/examples/validate-plan.cjs +127 -0
- package/skills/blueprint/presets/clinic-launch-a2p.md +39 -0
- package/skills/blueprint/presets/clinic-launch-a2p.preset.json +337 -0
- package/skills/blueprint/presets/generic-client.md +36 -0
- package/skills/blueprint/presets/generic-client.preset.json +255 -0
- package/skills/blueprint/presets/med-spa.md +59 -0
- package/skills/blueprint/presets/med-spa.preset.json +264 -0
- package/skills/blueprint/references/agency-os-detection.md +80 -0
- package/skills/blueprint/references/approval-view.md +83 -0
- package/skills/blueprint/references/brief-schema.md +45 -0
- package/skills/blueprint/references/build-plan-schema.md +52 -0
- package/skills/blueprint/references/external-funnel.md +310 -0
- package/skills/blueprint/references/intake-question-set.md +141 -0
- package/skills/blueprint/references/preset-format.md +130 -0
package/dist/index.js
CHANGED
|
@@ -114,8 +114,8 @@ var init_ghl_client = __esm({
|
|
|
114
114
|
Version: version || GHL_API_VERSION
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
-
buildUrl(
|
|
118
|
-
const url = new URL(
|
|
117
|
+
buildUrl(path12, params) {
|
|
118
|
+
const url = new URL(path12, GHL_BASE_URL);
|
|
119
119
|
if (params) {
|
|
120
120
|
for (const [key, value] of Object.entries(params)) {
|
|
121
121
|
if (value !== void 0 && value !== null) {
|
|
@@ -125,8 +125,8 @@ var init_ghl_client = __esm({
|
|
|
125
125
|
}
|
|
126
126
|
return url.toString();
|
|
127
127
|
}
|
|
128
|
-
async request(method,
|
|
129
|
-
const url = this.buildUrl(
|
|
128
|
+
async request(method, path12, options = {}, attempt = 0) {
|
|
129
|
+
const url = this.buildUrl(path12, options.params);
|
|
130
130
|
const headers = this.buildHeaders(options.version);
|
|
131
131
|
const fetchOptions = {
|
|
132
132
|
method,
|
|
@@ -144,14 +144,14 @@ var init_ghl_client = __esm({
|
|
|
144
144
|
} catch (error) {
|
|
145
145
|
clearTimeout(timeout);
|
|
146
146
|
if (error instanceof Error && error.name === "AbortError") {
|
|
147
|
-
throw new Error(`Request timeout (30s): ${method} ${
|
|
147
|
+
throw new Error(`Request timeout (30s): ${method} ${path12}`);
|
|
148
148
|
}
|
|
149
149
|
if (!options.noRetry && attempt < MAX_RETRIES) {
|
|
150
150
|
const delay4 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
|
|
151
|
-
process.stderr.write(`[ghl-mcp] Network error on ${method} ${
|
|
151
|
+
process.stderr.write(`[ghl-mcp] Network error on ${method} ${path12}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
|
|
152
152
|
`);
|
|
153
153
|
await new Promise((r) => setTimeout(r, delay4));
|
|
154
|
-
return this.request(method,
|
|
154
|
+
return this.request(method, path12, options, attempt + 1);
|
|
155
155
|
}
|
|
156
156
|
throw error;
|
|
157
157
|
} finally {
|
|
@@ -159,10 +159,10 @@ var init_ghl_client = __esm({
|
|
|
159
159
|
}
|
|
160
160
|
if (!options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
|
|
161
161
|
const delay4 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
|
|
162
|
-
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${
|
|
162
|
+
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path12}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay4}ms
|
|
163
163
|
`);
|
|
164
164
|
await new Promise((r) => setTimeout(r, delay4));
|
|
165
|
-
return this.request(method,
|
|
165
|
+
return this.request(method, path12, options, attempt + 1);
|
|
166
166
|
}
|
|
167
167
|
if (!response.ok) {
|
|
168
168
|
let errorBody = "";
|
|
@@ -171,7 +171,7 @@ var init_ghl_client = __esm({
|
|
|
171
171
|
} catch {
|
|
172
172
|
}
|
|
173
173
|
throw new Error(
|
|
174
|
-
`GHL API Error ${response.status} ${response.statusText}: ${method} ${
|
|
174
|
+
`GHL API Error ${response.status} ${response.statusText}: ${method} ${path12}
|
|
175
175
|
${errorBody}`
|
|
176
176
|
);
|
|
177
177
|
}
|
|
@@ -183,20 +183,20 @@ ${errorBody}`
|
|
|
183
183
|
return { message: text };
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
async get(
|
|
187
|
-
return this.request("GET",
|
|
186
|
+
async get(path12, options) {
|
|
187
|
+
return this.request("GET", path12, options);
|
|
188
188
|
}
|
|
189
|
-
async post(
|
|
190
|
-
return this.request("POST",
|
|
189
|
+
async post(path12, options) {
|
|
190
|
+
return this.request("POST", path12, options);
|
|
191
191
|
}
|
|
192
|
-
async put(
|
|
193
|
-
return this.request("PUT",
|
|
192
|
+
async put(path12, options) {
|
|
193
|
+
return this.request("PUT", path12, options);
|
|
194
194
|
}
|
|
195
|
-
async patch(
|
|
196
|
-
return this.request("PATCH",
|
|
195
|
+
async patch(path12, options) {
|
|
196
|
+
return this.request("PATCH", path12, options);
|
|
197
197
|
}
|
|
198
|
-
async delete(
|
|
199
|
-
return this.request("DELETE",
|
|
198
|
+
async delete(path12, options) {
|
|
199
|
+
return this.request("DELETE", path12, options);
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Helper: resolves locationId from args or falls back to default
|
|
@@ -998,9 +998,9 @@ function planFromPayload(payload) {
|
|
|
998
998
|
return payload?.plan === "command-os" ? "command-os" : "mcp";
|
|
999
999
|
}
|
|
1000
1000
|
function legacyDeviceFingerprint() {
|
|
1001
|
-
const
|
|
1001
|
+
const os6 = require("node:os");
|
|
1002
1002
|
const crypto4 = require("node:crypto");
|
|
1003
|
-
const raw = `${
|
|
1003
|
+
const raw = `${os6.hostname()}:${os6.userInfo().username}:${os6.platform()}:${os6.arch()}`;
|
|
1004
1004
|
return crypto4.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
1005
1005
|
}
|
|
1006
1006
|
function deviceFingerprint(opts) {
|
|
@@ -1235,7 +1235,7 @@ async function validateFirebase(firebaseKey, refreshToken) {
|
|
|
1235
1235
|
function registerSetupTool(server2) {
|
|
1236
1236
|
server2.tool(
|
|
1237
1237
|
"setup_ghl_mcp",
|
|
1238
|
-
"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
|
|
1238
|
+
"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 235 tools (181 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
|
|
1239
1239
|
{
|
|
1240
1240
|
email: import_zod40.z.string().email().describe("Email used at purchase."),
|
|
1241
1241
|
license_key: import_zod40.z.string().min(20).describe("License key from your purchase email."),
|
|
@@ -1317,11 +1317,11 @@ Note: Firebase credentials rejected (${fb.error}). Saved without Workflow Builde
|
|
|
1317
1317
|
signed_attestation: lic.signedAttestation
|
|
1318
1318
|
});
|
|
1319
1319
|
const isFree = lic.tier === "free";
|
|
1320
|
-
const toolCount = isFree ? workflowBuilderEnabled ? "
|
|
1320
|
+
const toolCount = isFree ? workflowBuilderEnabled ? "107" : "89" : workflowBuilderEnabled ? "235" : "181";
|
|
1321
1321
|
const wfLine = workflowBuilderEnabled ? "Workflow Builder: enabled." : "Workflow Builder: not configured (optional).";
|
|
1322
1322
|
const wfTip = workflowBuilderEnabled ? "" : isFree ? `
|
|
1323
1323
|
Unlock the account auditor next (the free tier's best tool): say "Unlock the Workflow Builder" \u2014 one browser login enables audit_workflows, validate_workflow, and the full-detail reads.` : "\nTo enable Workflow Builder later (52 extra Firebase-gated tools): run enable_workflow_builder with your three Firebase values. No need to re-enter license/API key/location ID.";
|
|
1324
|
-
const freeTip = isFree ? "\n\nFree tier:
|
|
1324
|
+
const freeTip = isFree ? "\n\nFree tier: 107 read-only tools (89 before the one-login auditor unlock). Write tools stay visible but answer with upgrade info instead of acting. Full version ($97/mo founding rate, same key upgrades in place): https://ghlcommand.com" : "";
|
|
1325
1325
|
return {
|
|
1326
1326
|
content: [{
|
|
1327
1327
|
type: "text",
|
|
@@ -1347,7 +1347,7 @@ Unlock the account auditor next (the free tier's best tool): say "Unlock the Wor
|
|
|
1347
1347
|
function registerEnableWorkflowBuilderTool(server2) {
|
|
1348
1348
|
server2.tool(
|
|
1349
1349
|
"enable_workflow_builder",
|
|
1350
|
-
"Add Firebase credentials to an existing GHL Command install to unlock 54 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. On the FREE tier this same login unlocks the read-only auditor suite (audit_workflows, validate_workflow, full-detail workflow/funnel/pipeline reads). Requires you've already run setup_ghl_mcp. EASIEST PATH: run `capture_firebase_interactive` instead \u2014 a Chrome window opens, you log into GHL, zero pasting. Use THIS tool when you have JSON from `auto_capture_firebase_script` (console-paste path) to put in `firebase_paste`, or the three manual DevTools fields. Tool count goes from
|
|
1350
|
+
"Add Firebase credentials to an existing GHL Command install to unlock 54 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. On the FREE tier this same login unlocks the read-only auditor suite (audit_workflows, validate_workflow, full-detail workflow/funnel/pipeline reads). Requires you've already run setup_ghl_mcp. EASIEST PATH: run `capture_firebase_interactive` instead \u2014 a Chrome window opens, you log into GHL, zero pasting. Use THIS tool when you have JSON from `auto_capture_firebase_script` (console-paste path) to put in `firebase_paste`, or the three manual DevTools fields. Tool count goes from 181 to 235 after the next Claude restart.",
|
|
1351
1351
|
{
|
|
1352
1352
|
// v3.25.0: one-paste path. Tool runs `auto_capture_firebase_script` to
|
|
1353
1353
|
// get the console script; the script returns a JSON object that pastes
|
|
@@ -1428,7 +1428,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
|
|
|
1428
1428
|
"",
|
|
1429
1429
|
"**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.",
|
|
1430
1430
|
"",
|
|
1431
|
-
'After restart, all
|
|
1431
|
+
'After restart, all 235 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
|
|
1432
1432
|
"",
|
|
1433
1433
|
"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."
|
|
1434
1434
|
].join("\n")
|
|
@@ -1524,7 +1524,7 @@ The login in the capture window may belong to the wrong GHL account, or the sess
|
|
|
1524
1524
|
"",
|
|
1525
1525
|
"**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen.",
|
|
1526
1526
|
"",
|
|
1527
|
-
'After restart, all
|
|
1527
|
+
'After restart, all 235 tools load. Try: "List my workflows in full detail".',
|
|
1528
1528
|
"",
|
|
1529
1529
|
"Future token rotations re-capture silently \u2014 if workflow tools ever 401, just run capture_firebase_interactive again; no window should appear."
|
|
1530
1530
|
].join("\n")
|
|
@@ -2102,9 +2102,9 @@ var require_package = __commonJS({
|
|
|
2102
2102
|
"package.json"(exports2, module2) {
|
|
2103
2103
|
module2.exports = {
|
|
2104
2104
|
name: "@elitedcs/ghl-mcp",
|
|
2105
|
-
version: "3.
|
|
2105
|
+
version: "3.55.0",
|
|
2106
2106
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
2107
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
2107
|
+
description: "GoHighLevel MCP Server for Claude. 235 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.",
|
|
2108
2108
|
main: "dist/index.js",
|
|
2109
2109
|
bin: {
|
|
2110
2110
|
"ghl-mcp": "dist/index.js"
|
|
@@ -2118,7 +2118,8 @@ var require_package = __commonJS({
|
|
|
2118
2118
|
"templates/external-funnel/cloudflare-worker.js",
|
|
2119
2119
|
"templates/external-funnel/README.md",
|
|
2120
2120
|
"README.md",
|
|
2121
|
-
"CHANGELOG.md"
|
|
2121
|
+
"CHANGELOG.md",
|
|
2122
|
+
"skills"
|
|
2122
2123
|
],
|
|
2123
2124
|
scripts: {
|
|
2124
2125
|
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",
|
|
@@ -2191,7 +2192,7 @@ function launchCommand(port = 7300) {
|
|
|
2191
2192
|
return `npx -y @elitedcs/ghl-mcp@latest dashboard --port=${port}`;
|
|
2192
2193
|
}
|
|
2193
2194
|
function installingEntry() {
|
|
2194
|
-
return
|
|
2195
|
+
return path8.join(__dirname, "index.js");
|
|
2195
2196
|
}
|
|
2196
2197
|
function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
2197
2198
|
const cmd = launchCommand(port);
|
|
@@ -2199,12 +2200,12 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2199
2200
|
const systemApps = "/Applications";
|
|
2200
2201
|
let base = home;
|
|
2201
2202
|
try {
|
|
2202
|
-
|
|
2203
|
+
fs8.accessSync(systemApps, fs8.constants.W_OK);
|
|
2203
2204
|
base = "";
|
|
2204
2205
|
} catch {
|
|
2205
2206
|
}
|
|
2206
|
-
const appDir = base ?
|
|
2207
|
-
const macOSDir =
|
|
2207
|
+
const appDir = base ? path8.join(home, "Applications", "Command OS.app") : path8.join(systemApps, "Command OS.app");
|
|
2208
|
+
const macOSDir = path8.join(appDir, "Contents", "MacOS");
|
|
2208
2209
|
const script = [
|
|
2209
2210
|
"#!/bin/bash",
|
|
2210
2211
|
"# GHL Command \u2014 Command OS launcher (regenerate: ghl-mcp install-launcher)",
|
|
@@ -2233,13 +2234,13 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2233
2234
|
targetPath: appDir,
|
|
2234
2235
|
humanLocation: base ? "your personal Applications folder at ~/Applications (Finder \u2192 Go \u2192 Home \u2192 Applications)" : "your Applications folder \u2014 open Finder \u2192 Applications and look for \u201CCommand OS\u201D",
|
|
2235
2236
|
files: [
|
|
2236
|
-
{ path:
|
|
2237
|
-
{ path:
|
|
2237
|
+
{ path: path8.join(macOSDir, "command-os"), contents: script, executable: true },
|
|
2238
|
+
{ path: path8.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
|
|
2238
2239
|
]
|
|
2239
2240
|
};
|
|
2240
2241
|
}
|
|
2241
2242
|
if (platform2 === "win32") {
|
|
2242
|
-
const target2 =
|
|
2243
|
+
const target2 = path8.join(home, "Desktop", "Command OS.cmd");
|
|
2243
2244
|
return {
|
|
2244
2245
|
platform: platform2,
|
|
2245
2246
|
targetPath: target2,
|
|
@@ -2252,7 +2253,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2252
2253
|
].join("\r\n"), executable: false }]
|
|
2253
2254
|
};
|
|
2254
2255
|
}
|
|
2255
|
-
const target =
|
|
2256
|
+
const target = path8.join(home, ".local", "share", "applications", "command-os.desktop");
|
|
2256
2257
|
return {
|
|
2257
2258
|
platform: platform2,
|
|
2258
2259
|
targetPath: target,
|
|
@@ -2276,11 +2277,11 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2276
2277
|
function installLauncher(argv = []) {
|
|
2277
2278
|
const portArg = argv.find((a) => a.startsWith("--port="));
|
|
2278
2279
|
const port = portArg ? Number(portArg.split("=")[1]) : 7300;
|
|
2279
|
-
const plan = planLauncher(process.platform,
|
|
2280
|
+
const plan = planLauncher(process.platform, os4.homedir(), port, installingEntry());
|
|
2280
2281
|
try {
|
|
2281
2282
|
for (const f of plan.files) {
|
|
2282
|
-
|
|
2283
|
-
|
|
2283
|
+
fs8.mkdirSync(path8.dirname(f.path), { recursive: true });
|
|
2284
|
+
fs8.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
|
|
2284
2285
|
}
|
|
2285
2286
|
} catch (e) {
|
|
2286
2287
|
process.stderr.write(`
|
|
@@ -2300,13 +2301,13 @@ function installLauncher(argv = []) {
|
|
|
2300
2301
|
].join("\n"));
|
|
2301
2302
|
return 0;
|
|
2302
2303
|
}
|
|
2303
|
-
var
|
|
2304
|
+
var fs8, os4, path8;
|
|
2304
2305
|
var init_launcher = __esm({
|
|
2305
2306
|
"src/launcher.ts"() {
|
|
2306
2307
|
"use strict";
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2308
|
+
fs8 = __toESM(require("fs"));
|
|
2309
|
+
os4 = __toESM(require("os"));
|
|
2310
|
+
path8 = __toESM(require("path"));
|
|
2310
2311
|
}
|
|
2311
2312
|
});
|
|
2312
2313
|
|
|
@@ -2335,10 +2336,10 @@ function stageBlockedBy(stages, stage) {
|
|
|
2335
2336
|
return null;
|
|
2336
2337
|
}
|
|
2337
2338
|
function claudeBin() {
|
|
2338
|
-
const fallback =
|
|
2339
|
-
const fromPath = (process.env.PATH || "").split(
|
|
2339
|
+
const fallback = path9.join(os5.homedir(), ".local", "bin", "claude");
|
|
2340
|
+
const fromPath = (process.env.PATH || "").split(path9.delimiter).map((d) => path9.join(d, "claude")).find((p) => {
|
|
2340
2341
|
try {
|
|
2341
|
-
|
|
2342
|
+
fs9.accessSync(p, fs9.constants.X_OK);
|
|
2342
2343
|
return true;
|
|
2343
2344
|
} catch {
|
|
2344
2345
|
return false;
|
|
@@ -2346,7 +2347,7 @@ function claudeBin() {
|
|
|
2346
2347
|
});
|
|
2347
2348
|
if (fromPath) return fromPath;
|
|
2348
2349
|
try {
|
|
2349
|
-
|
|
2350
|
+
fs9.accessSync(fallback, fs9.constants.X_OK);
|
|
2350
2351
|
return fallback;
|
|
2351
2352
|
} catch {
|
|
2352
2353
|
}
|
|
@@ -2395,7 +2396,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
2395
2396
|
const locationName = SANDBOX_ALLOWLIST[locationId2] || opts.name || locationId2;
|
|
2396
2397
|
const spec = STAGE_SPECS[stage];
|
|
2397
2398
|
if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
|
|
2398
|
-
return new Promise((
|
|
2399
|
+
return new Promise((resolve6) => {
|
|
2399
2400
|
const bin = claudeBin();
|
|
2400
2401
|
onEvent({ kind: "status", line: `Starting ${spec.name} on ${locationName} (headless Claude, ${spec.allowedTools.length} tools allowed)\u2026` });
|
|
2401
2402
|
const child = (0, import_child_process2.spawn)(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context), {
|
|
@@ -2441,31 +2442,31 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
2441
2442
|
if (code !== 0 && !lastText) {
|
|
2442
2443
|
const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}` };
|
|
2443
2444
|
onEvent({ kind: "error", line: outcome2.error });
|
|
2444
|
-
|
|
2445
|
+
resolve6(outcome2);
|
|
2445
2446
|
return;
|
|
2446
2447
|
}
|
|
2447
2448
|
const outcome = parseResultLine(lastText);
|
|
2448
2449
|
const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
|
|
2449
2450
|
const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
|
|
2450
2451
|
onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
|
|
2451
|
-
|
|
2452
|
+
resolve6(outcome);
|
|
2452
2453
|
});
|
|
2453
2454
|
child.on("error", (err) => {
|
|
2454
2455
|
clearTimeout(timeout);
|
|
2455
2456
|
const outcome = { ok: false, error: `could not start claude: ${err.message}` };
|
|
2456
2457
|
onEvent({ kind: "error", line: outcome.error });
|
|
2457
|
-
|
|
2458
|
+
resolve6(outcome);
|
|
2458
2459
|
});
|
|
2459
2460
|
});
|
|
2460
2461
|
}
|
|
2461
|
-
var import_child_process2,
|
|
2462
|
+
var import_child_process2, fs9, os5, path9, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAGE_SPECS, HUMAN_GATE_STAGES;
|
|
2462
2463
|
var init_stage_runner = __esm({
|
|
2463
2464
|
"src/stage-runner.ts"() {
|
|
2464
2465
|
"use strict";
|
|
2465
2466
|
import_child_process2 = require("child_process");
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2467
|
+
fs9 = __toESM(require("fs"));
|
|
2468
|
+
os5 = __toESM(require("os"));
|
|
2469
|
+
path9 = __toESM(require("path"));
|
|
2469
2470
|
SANDBOX_ALLOWLIST = {
|
|
2470
2471
|
JrV2p35O3hY2wqhr2c0T: "MCP Testing",
|
|
2471
2472
|
jHP5wkYRineXDlzAOEbW: "Blueprint Demo"
|
|
@@ -3185,11 +3186,11 @@ __export(dashboard_exports, {
|
|
|
3185
3186
|
writeOverlay: () => writeOverlay
|
|
3186
3187
|
});
|
|
3187
3188
|
function overlayPath() {
|
|
3188
|
-
return
|
|
3189
|
+
return path10.join(appDataDir(), "intake-overlay.json");
|
|
3189
3190
|
}
|
|
3190
3191
|
function readOverlay() {
|
|
3191
3192
|
try {
|
|
3192
|
-
const raw = JSON.parse(
|
|
3193
|
+
const raw = JSON.parse(fs10.readFileSync(overlayPath(), "utf8"));
|
|
3193
3194
|
if (raw && typeof raw === "object") return raw;
|
|
3194
3195
|
} catch {
|
|
3195
3196
|
}
|
|
@@ -3197,7 +3198,7 @@ function readOverlay() {
|
|
|
3197
3198
|
}
|
|
3198
3199
|
function writeOverlay(layer) {
|
|
3199
3200
|
ensureAppDataDir();
|
|
3200
|
-
|
|
3201
|
+
fs10.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
|
|
3201
3202
|
}
|
|
3202
3203
|
function recoverStuckStages(state) {
|
|
3203
3204
|
let recovered = 0;
|
|
@@ -3209,11 +3210,11 @@ function recoverStuckStages(state) {
|
|
|
3209
3210
|
return { state: { ...state, clients }, recovered };
|
|
3210
3211
|
}
|
|
3211
3212
|
function cockpitStatePath() {
|
|
3212
|
-
return
|
|
3213
|
+
return path10.join(appDataDir(), "cockpit-state.json");
|
|
3213
3214
|
}
|
|
3214
3215
|
function readCockpitState() {
|
|
3215
3216
|
try {
|
|
3216
|
-
const raw = JSON.parse(
|
|
3217
|
+
const raw = JSON.parse(fs10.readFileSync(cockpitStatePath(), "utf8"));
|
|
3217
3218
|
if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
|
|
3218
3219
|
} catch {
|
|
3219
3220
|
}
|
|
@@ -3221,7 +3222,7 @@ function readCockpitState() {
|
|
|
3221
3222
|
}
|
|
3222
3223
|
function writeCockpitState(state) {
|
|
3223
3224
|
ensureAppDataDir();
|
|
3224
|
-
|
|
3225
|
+
fs10.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
3225
3226
|
}
|
|
3226
3227
|
function setStage(state, locationId2, stageIndex, status) {
|
|
3227
3228
|
if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES.length) throw new Error("bad stage index");
|
|
@@ -3313,7 +3314,7 @@ function notify(message) {
|
|
|
3313
3314
|
`);
|
|
3314
3315
|
try {
|
|
3315
3316
|
ensureAppDataDir();
|
|
3316
|
-
|
|
3317
|
+
fs10.appendFileSync(path10.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
3317
3318
|
`);
|
|
3318
3319
|
} catch {
|
|
3319
3320
|
}
|
|
@@ -4014,17 +4015,17 @@ async function runDashboard(argv) {
|
|
|
4014
4015
|
const source = args.url ? { kind: "url", url: String(args.url) } : args.instruction ? { kind: "recorder", instruction: String(args.instruction) } : { kind: "text", text: String(args.transcript ?? "") };
|
|
4015
4016
|
let recorderPatterns = [];
|
|
4016
4017
|
if (source.kind === "recorder") {
|
|
4017
|
-
recorderPatterns = await new Promise((
|
|
4018
|
+
recorderPatterns = await new Promise((resolve6) => {
|
|
4018
4019
|
const ls = (0, import_child_process4.spawn)(claudeBin(), ["mcp", "list"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4019
4020
|
let buf = "";
|
|
4020
4021
|
ls.stdout.on("data", (d) => {
|
|
4021
4022
|
buf += d.toString();
|
|
4022
4023
|
});
|
|
4023
|
-
ls.on("close", () =>
|
|
4024
|
-
ls.on("error", () =>
|
|
4024
|
+
ls.on("close", () => resolve6(recorderAllowPatterns(buf)));
|
|
4025
|
+
ls.on("error", () => resolve6([]));
|
|
4025
4026
|
setTimeout(() => {
|
|
4026
4027
|
ls.kill("SIGKILL");
|
|
4027
|
-
|
|
4028
|
+
resolve6(recorderAllowPatterns(buf));
|
|
4028
4029
|
}, 9e4).unref?.();
|
|
4029
4030
|
});
|
|
4030
4031
|
if (!recorderPatterns.length) {
|
|
@@ -4034,7 +4035,7 @@ async function runDashboard(argv) {
|
|
|
4034
4035
|
}
|
|
4035
4036
|
}
|
|
4036
4037
|
const out = await prefillFromSource(
|
|
4037
|
-
(prompt, tools) => new Promise((
|
|
4038
|
+
(prompt, tools) => new Promise((resolve6, reject) => {
|
|
4038
4039
|
const argv2 = ["-p", prompt, "--max-turns", "25", "--disallowedTools", tools.deny];
|
|
4039
4040
|
if (tools.allow) argv2.push("--allowedTools", tools.allow);
|
|
4040
4041
|
else argv2.push("--allowedTools", "");
|
|
@@ -4043,7 +4044,7 @@ async function runDashboard(argv) {
|
|
|
4043
4044
|
child.stdout.on("data", (d) => {
|
|
4044
4045
|
out2 += d.toString();
|
|
4045
4046
|
});
|
|
4046
|
-
child.on("close", () =>
|
|
4047
|
+
child.on("close", () => resolve6(out2));
|
|
4047
4048
|
child.on("error", reject);
|
|
4048
4049
|
setTimeout(() => child.kill("SIGKILL"), 5 * 60 * 1e3).unref?.();
|
|
4049
4050
|
}),
|
|
@@ -4175,13 +4176,13 @@ async function runDashboard(argv) {
|
|
|
4175
4176
|
}
|
|
4176
4177
|
}
|
|
4177
4178
|
const review = await generateReview(
|
|
4178
|
-
(prompt) => new Promise((
|
|
4179
|
+
(prompt) => new Promise((resolve6, reject) => {
|
|
4179
4180
|
const child = (0, import_child_process4.spawn)(claudeBin(), ["-p", prompt, "--allowedTools", "", "--disallowedTools", "Bash,Write,Edit,WebFetch,WebSearch,Task,Read,Glob,Grep", "--max-turns", "6"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
4180
4181
|
let out = "";
|
|
4181
4182
|
child.stdout.on("data", (d) => {
|
|
4182
4183
|
out += d.toString();
|
|
4183
4184
|
});
|
|
4184
|
-
child.on("close", () =>
|
|
4185
|
+
child.on("close", () => resolve6(out));
|
|
4185
4186
|
child.on("error", reject);
|
|
4186
4187
|
setTimeout(() => child.kill("SIGKILL"), 6 * 60 * 1e3).unref?.();
|
|
4187
4188
|
}),
|
|
@@ -4354,9 +4355,9 @@ async function runDashboard(argv) {
|
|
|
4354
4355
|
res.end();
|
|
4355
4356
|
});
|
|
4356
4357
|
try {
|
|
4357
|
-
await new Promise((
|
|
4358
|
+
await new Promise((resolve6, reject) => {
|
|
4358
4359
|
server2.once("error", reject);
|
|
4359
|
-
server2.listen(port, "127.0.0.1",
|
|
4360
|
+
server2.listen(port, "127.0.0.1", resolve6);
|
|
4360
4361
|
});
|
|
4361
4362
|
} catch (e) {
|
|
4362
4363
|
const msg2 = e?.code === "EADDRINUSE" ? `Port ${port} is busy with another program. Close it, or start Command OS on a different port.` : `Command OS could not start: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -4382,18 +4383,18 @@ async function runDashboard(argv) {
|
|
|
4382
4383
|
if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
|
|
4383
4384
|
`);
|
|
4384
4385
|
});
|
|
4385
|
-
return await new Promise((
|
|
4386
|
-
const stop = () => server2.close(() =>
|
|
4386
|
+
return await new Promise((resolve6) => {
|
|
4387
|
+
const stop = () => server2.close(() => resolve6(0));
|
|
4387
4388
|
process.on("SIGINT", stop);
|
|
4388
4389
|
process.on("SIGTERM", stop);
|
|
4389
4390
|
});
|
|
4390
4391
|
}
|
|
4391
|
-
var
|
|
4392
|
+
var fs10, path10, http, import_child_process3, import_child_process4, osmod, STAGES, ACCOUNT_TYPES, activeRun, UPGRADE_MSG;
|
|
4392
4393
|
var init_dashboard = __esm({
|
|
4393
4394
|
"src/dashboard.ts"() {
|
|
4394
4395
|
"use strict";
|
|
4395
|
-
|
|
4396
|
-
|
|
4396
|
+
fs10 = __toESM(require("fs"));
|
|
4397
|
+
path10 = __toESM(require("path"));
|
|
4397
4398
|
http = __toESM(require("http"));
|
|
4398
4399
|
import_child_process3 = require("child_process");
|
|
4399
4400
|
init_credentials_store();
|
|
@@ -4428,8 +4429,8 @@ var init_dashboard = __esm({
|
|
|
4428
4429
|
|
|
4429
4430
|
// src/index.ts
|
|
4430
4431
|
var dotenv2 = __toESM(require("dotenv"));
|
|
4431
|
-
var
|
|
4432
|
-
var
|
|
4432
|
+
var path11 = __toESM(require("path"));
|
|
4433
|
+
var fs11 = __toESM(require("fs"));
|
|
4433
4434
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4434
4435
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4435
4436
|
init_ghl_client();
|
|
@@ -8376,16 +8377,16 @@ function registerEmailTools(server2, client) {
|
|
|
8376
8377
|
function registerEmailBuilderInternalTools(server2, builderClient) {
|
|
8377
8378
|
const client = builderClient;
|
|
8378
8379
|
if (!client) return;
|
|
8379
|
-
async function builderRequest(method,
|
|
8380
|
+
async function builderRequest(method, path12, body) {
|
|
8380
8381
|
const headers = await client.buildHeaders();
|
|
8381
|
-
const response = await fetch(`${EMAIL_BUILDER_BASE}${
|
|
8382
|
+
const response = await fetch(`${EMAIL_BUILDER_BASE}${path12}`, {
|
|
8382
8383
|
method,
|
|
8383
8384
|
headers,
|
|
8384
8385
|
body: body ? JSON.stringify(body) : void 0
|
|
8385
8386
|
});
|
|
8386
8387
|
if (!response.ok) {
|
|
8387
8388
|
const text2 = await response.text();
|
|
8388
|
-
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${
|
|
8389
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path12}
|
|
8389
8390
|
${text2}`);
|
|
8390
8391
|
}
|
|
8391
8392
|
const text = await response.text();
|
|
@@ -9642,23 +9643,23 @@ var import_zod34 = require("zod");
|
|
|
9642
9643
|
function registerFunnelBuilderTools(server2, builderClient) {
|
|
9643
9644
|
const client = builderClient;
|
|
9644
9645
|
if (!client) return;
|
|
9645
|
-
async function internalGet(
|
|
9646
|
-
return client.request("GET",
|
|
9646
|
+
async function internalGet(path12) {
|
|
9647
|
+
return client.request("GET", path12);
|
|
9647
9648
|
}
|
|
9648
|
-
async function internalPost(
|
|
9649
|
-
return client.request("POST",
|
|
9649
|
+
async function internalPost(path12, body) {
|
|
9650
|
+
return client.request("POST", path12, body);
|
|
9650
9651
|
}
|
|
9651
|
-
async function internalPut(
|
|
9652
|
-
return client.request("PUT",
|
|
9652
|
+
async function internalPut(path12, body) {
|
|
9653
|
+
return client.request("PUT", path12, body);
|
|
9653
9654
|
}
|
|
9654
|
-
async function internalDelete(
|
|
9655
|
-
return client.request("DELETE",
|
|
9655
|
+
async function internalDelete(path12) {
|
|
9656
|
+
return client.request("DELETE", path12);
|
|
9656
9657
|
}
|
|
9657
|
-
async function funnelRequest(method,
|
|
9658
|
+
async function funnelRequest(method, path12, body) {
|
|
9658
9659
|
const headers = await client.buildHeaders();
|
|
9659
9660
|
headers.Origin = "https://app.gohighlevel.com";
|
|
9660
9661
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
9661
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
9662
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path12}`;
|
|
9662
9663
|
const options = { method, headers };
|
|
9663
9664
|
if (body && (method === "POST" || method === "PUT")) {
|
|
9664
9665
|
options.body = JSON.stringify(body);
|
|
@@ -9666,7 +9667,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
9666
9667
|
const response = await fetch(url, options);
|
|
9667
9668
|
if (!response.ok) {
|
|
9668
9669
|
const text2 = await response.text();
|
|
9669
|
-
throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
9670
|
+
throw new Error(`Funnel API Error ${response.status}: ${method} ${path12}
|
|
9670
9671
|
${text2}`);
|
|
9671
9672
|
}
|
|
9672
9673
|
const text = await response.text();
|
|
@@ -10611,12 +10612,12 @@ var valueCardSchema = import_zod35.z.object({
|
|
|
10611
10612
|
function registerPageStudioTools(server2, builderClient) {
|
|
10612
10613
|
const client = builderClient;
|
|
10613
10614
|
if (!client) return;
|
|
10614
|
-
async function funnelRequest(method,
|
|
10615
|
+
async function funnelRequest(method, path12) {
|
|
10615
10616
|
const headers = await client.buildHeaders();
|
|
10616
10617
|
headers.Origin = "https://app.gohighlevel.com";
|
|
10617
10618
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
10618
|
-
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${
|
|
10619
|
-
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
10619
|
+
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${path12}`, { method, headers });
|
|
10620
|
+
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path12}
|
|
10620
10621
|
${await response.text()}`);
|
|
10621
10622
|
const text = await response.text();
|
|
10622
10623
|
return text ? JSON.parse(text) : {};
|
|
@@ -10935,9 +10936,9 @@ function buildUpdateFormPath(formId, locationId2) {
|
|
|
10935
10936
|
function buildUpdateFormBody(name, formData) {
|
|
10936
10937
|
return { name, formData };
|
|
10937
10938
|
}
|
|
10938
|
-
async function formApiRequest(client, method,
|
|
10939
|
+
async function formApiRequest(client, method, path12, body) {
|
|
10939
10940
|
const headers = await client.buildHeaders();
|
|
10940
|
-
const url = `https://backend.leadconnectorhq.com/forms${
|
|
10941
|
+
const url = `https://backend.leadconnectorhq.com/forms${path12}`;
|
|
10941
10942
|
const options = { method, headers };
|
|
10942
10943
|
if (body && (method === "POST" || method === "PUT")) {
|
|
10943
10944
|
options.body = JSON.stringify(body);
|
|
@@ -10945,7 +10946,7 @@ async function formApiRequest(client, method, path11, body) {
|
|
|
10945
10946
|
const response = await fetch(url, options);
|
|
10946
10947
|
if (!response.ok) {
|
|
10947
10948
|
const text2 = await response.text();
|
|
10948
|
-
throw new Error(`Form API Error ${response.status}: ${method} ${
|
|
10949
|
+
throw new Error(`Form API Error ${response.status}: ${method} ${path12}
|
|
10949
10950
|
${text2}`);
|
|
10950
10951
|
}
|
|
10951
10952
|
const text = await response.text();
|
|
@@ -10959,7 +10960,7 @@ ${text2}`);
|
|
|
10959
10960
|
function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
10960
10961
|
const client = builderClient;
|
|
10961
10962
|
if (!client) return;
|
|
10962
|
-
const formRequest = (method,
|
|
10963
|
+
const formRequest = (method, path12, body) => formApiRequest(client, method, path12, body);
|
|
10963
10964
|
server2.tool(
|
|
10964
10965
|
"get_form_full",
|
|
10965
10966
|
"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.",
|
|
@@ -11080,10 +11081,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
11080
11081
|
},
|
|
11081
11082
|
async ({ formId, limit, skip }) => {
|
|
11082
11083
|
try {
|
|
11083
|
-
let
|
|
11084
|
-
if (formId)
|
|
11085
|
-
if (skip)
|
|
11086
|
-
const result = await formRequest("GET",
|
|
11084
|
+
let path12 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
|
|
11085
|
+
if (formId) path12 += `&formId=${formId}`;
|
|
11086
|
+
if (skip) path12 += `&skip=${skip}`;
|
|
11087
|
+
const result = await formRequest("GET", path12);
|
|
11087
11088
|
return {
|
|
11088
11089
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
11089
11090
|
};
|
|
@@ -11476,9 +11477,9 @@ var import_zod38 = require("zod");
|
|
|
11476
11477
|
function registerPipelineBuilderTools(server2, builderClient) {
|
|
11477
11478
|
const client = builderClient;
|
|
11478
11479
|
if (!client) return;
|
|
11479
|
-
async function pipelineRequest(method,
|
|
11480
|
+
async function pipelineRequest(method, path12, body) {
|
|
11480
11481
|
const headers = await client.buildHeaders();
|
|
11481
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
11482
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path12}`;
|
|
11482
11483
|
const options = { method, headers };
|
|
11483
11484
|
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
11484
11485
|
options.body = JSON.stringify(body);
|
|
@@ -11486,7 +11487,7 @@ function registerPipelineBuilderTools(server2, builderClient) {
|
|
|
11486
11487
|
const response = await fetch(url, options);
|
|
11487
11488
|
if (!response.ok) {
|
|
11488
11489
|
const text2 = await response.text();
|
|
11489
|
-
throw new Error(`Pipeline API Error ${response.status}: ${method} ${
|
|
11490
|
+
throw new Error(`Pipeline API Error ${response.status}: ${method} ${path12}
|
|
11490
11491
|
${text2}`);
|
|
11491
11492
|
}
|
|
11492
11493
|
const text = await response.text();
|
|
@@ -12231,7 +12232,7 @@ ${lines.join("\n")}
|
|
|
12231
12232
|
// src/tools/bulk-operations.ts
|
|
12232
12233
|
var import_zod42 = require("zod");
|
|
12233
12234
|
function delay(ms) {
|
|
12234
|
-
return new Promise((
|
|
12235
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
12235
12236
|
}
|
|
12236
12237
|
function formatResults(op, results, total) {
|
|
12237
12238
|
return `${op}: ${results.success} success, ${results.failed} failed out of ${total}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
|
|
@@ -12356,7 +12357,7 @@ function registerBulkOperationTools(server2, client) {
|
|
|
12356
12357
|
// src/tools/account-export.ts
|
|
12357
12358
|
var import_zod43 = require("zod");
|
|
12358
12359
|
function delay2(ms) {
|
|
12359
|
-
return new Promise((
|
|
12360
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
12360
12361
|
}
|
|
12361
12362
|
function registerAccountExportTools(server2, client) {
|
|
12362
12363
|
const builderClient = WorkflowBuilderClient.fromEnv();
|
|
@@ -12677,9 +12678,9 @@ var OBJECT_KEYS = ["contacts", "opportunity"];
|
|
|
12677
12678
|
function registerSmartListTools(server2, builderClient) {
|
|
12678
12679
|
const client = builderClient;
|
|
12679
12680
|
if (!client) return;
|
|
12680
|
-
async function smartListRequest(method,
|
|
12681
|
+
async function smartListRequest(method, path12, body) {
|
|
12681
12682
|
const headers = await client.buildHeaders();
|
|
12682
|
-
const url = `${SMARTLIST_BASE}${
|
|
12683
|
+
const url = `${SMARTLIST_BASE}${path12}`;
|
|
12683
12684
|
const options = { method, headers };
|
|
12684
12685
|
if (body && (method === "POST" || method === "PUT")) {
|
|
12685
12686
|
options.body = JSON.stringify(body);
|
|
@@ -12687,7 +12688,7 @@ function registerSmartListTools(server2, builderClient) {
|
|
|
12687
12688
|
const response = await fetch(url, options);
|
|
12688
12689
|
if (!response.ok) {
|
|
12689
12690
|
const text2 = await response.text();
|
|
12690
|
-
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${
|
|
12691
|
+
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path12}
|
|
12691
12692
|
${text2}`);
|
|
12692
12693
|
}
|
|
12693
12694
|
const text = await response.text();
|
|
@@ -12819,12 +12820,12 @@ var REPUTATION_BASE = "https://backend.leadconnectorhq.com/reputation";
|
|
|
12819
12820
|
function registerReputationTools(server2, builderClient) {
|
|
12820
12821
|
const client = builderClient;
|
|
12821
12822
|
if (!client) return;
|
|
12822
|
-
async function reputationRequest(method,
|
|
12823
|
+
async function reputationRequest(method, path12) {
|
|
12823
12824
|
const headers = await client.buildHeaders();
|
|
12824
|
-
const response = await fetch(`${REPUTATION_BASE}${
|
|
12825
|
+
const response = await fetch(`${REPUTATION_BASE}${path12}`, { method, headers });
|
|
12825
12826
|
if (!response.ok) {
|
|
12826
12827
|
const text2 = await response.text();
|
|
12827
|
-
throw new Error(`Reputation API Error ${response.status}: ${method} ${
|
|
12828
|
+
throw new Error(`Reputation API Error ${response.status}: ${method} ${path12}
|
|
12828
12829
|
${text2}`);
|
|
12829
12830
|
}
|
|
12830
12831
|
const text = await response.text();
|
|
@@ -12939,16 +12940,16 @@ var MEMBERSHIP_BASE = "https://backend.leadconnectorhq.com/membership";
|
|
|
12939
12940
|
function registerMembershipTools(server2, builderClient) {
|
|
12940
12941
|
const client = builderClient;
|
|
12941
12942
|
if (!client) return;
|
|
12942
|
-
async function membershipRequest(
|
|
12943
|
+
async function membershipRequest(path12, method = "GET", body) {
|
|
12943
12944
|
const headers = await client.buildHeaders();
|
|
12944
|
-
const response = await fetch(`${MEMBERSHIP_BASE}${
|
|
12945
|
+
const response = await fetch(`${MEMBERSHIP_BASE}${path12}`, {
|
|
12945
12946
|
method,
|
|
12946
12947
|
headers,
|
|
12947
12948
|
body: body ? JSON.stringify(body) : void 0
|
|
12948
12949
|
});
|
|
12949
12950
|
if (!response.ok) {
|
|
12950
12951
|
const text2 = await response.text();
|
|
12951
|
-
throw new Error(`Membership API Error ${response.status}: ${method} ${
|
|
12952
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path12}
|
|
12952
12953
|
${text2}`);
|
|
12953
12954
|
}
|
|
12954
12955
|
const text = await response.text();
|
|
@@ -13117,7 +13118,7 @@ var import_zod49 = require("zod");
|
|
|
13117
13118
|
var fs5 = __toESM(require("fs"));
|
|
13118
13119
|
var path5 = __toESM(require("path"));
|
|
13119
13120
|
function delay3(ms) {
|
|
13120
|
-
return new Promise((
|
|
13121
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
13121
13122
|
}
|
|
13122
13123
|
var TemplateSchema = import_zod49.z.object({
|
|
13123
13124
|
templateName: import_zod49.z.string(),
|
|
@@ -13261,7 +13262,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
13261
13262
|
const locId = client.resolveLocationId(locationId2);
|
|
13262
13263
|
const safePath = validateTemplatePath(templateFile);
|
|
13263
13264
|
const template = TemplateSchema.parse(JSON.parse(fs5.readFileSync(safePath, "utf-8")));
|
|
13264
|
-
const
|
|
13265
|
+
const resolve6 = (text) => {
|
|
13265
13266
|
if (typeof text !== "string") return text;
|
|
13266
13267
|
let result = text;
|
|
13267
13268
|
for (const [key, value] of Object.entries(answers)) {
|
|
@@ -13274,7 +13275,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
13274
13275
|
return result;
|
|
13275
13276
|
};
|
|
13276
13277
|
const resolveObj = (obj) => {
|
|
13277
|
-
if (typeof obj === "string") return
|
|
13278
|
+
if (typeof obj === "string") return resolve6(obj);
|
|
13278
13279
|
if (Array.isArray(obj)) return obj.map(resolveObj);
|
|
13279
13280
|
if (obj && typeof obj === "object") {
|
|
13280
13281
|
const result = {};
|
|
@@ -14897,9 +14898,9 @@ function presetForBusinessType(type) {
|
|
|
14897
14898
|
return "generic";
|
|
14898
14899
|
}
|
|
14899
14900
|
}
|
|
14900
|
-
function setPath(target,
|
|
14901
|
+
function setPath(target, path12, value) {
|
|
14901
14902
|
if (value === void 0) return;
|
|
14902
|
-
const parts =
|
|
14903
|
+
const parts = path12.split(".");
|
|
14903
14904
|
let node = target;
|
|
14904
14905
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
14905
14906
|
const k = parts[i];
|
|
@@ -16985,15 +16986,15 @@ function extractFunnelId(result) {
|
|
|
16985
16986
|
return void 0;
|
|
16986
16987
|
}
|
|
16987
16988
|
function makeExecuteDeps(client, builderClient, locationId2) {
|
|
16988
|
-
const pipelineApi = async (method,
|
|
16989
|
+
const pipelineApi = async (method, path12, body) => {
|
|
16989
16990
|
const headers = await builderClient.buildHeaders();
|
|
16990
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
16991
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path12}`;
|
|
16991
16992
|
const options = { method, headers };
|
|
16992
16993
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
16993
16994
|
const response = await fetch(url, options);
|
|
16994
16995
|
if (!response.ok) {
|
|
16995
16996
|
const text2 = await response.text();
|
|
16996
|
-
throw new Error(`Pipeline API ${response.status}: ${method} ${
|
|
16997
|
+
throw new Error(`Pipeline API ${response.status}: ${method} ${path12}
|
|
16997
16998
|
${text2.slice(0, 300)}`);
|
|
16998
16999
|
}
|
|
16999
17000
|
const text = await response.text();
|
|
@@ -17004,17 +17005,17 @@ ${text2.slice(0, 300)}`);
|
|
|
17004
17005
|
return JSON.parse(text.replace(/[\x00-\x1F\x7F]/g, ""));
|
|
17005
17006
|
}
|
|
17006
17007
|
};
|
|
17007
|
-
const funnelApi = async (method,
|
|
17008
|
+
const funnelApi = async (method, path12, body) => {
|
|
17008
17009
|
const headers = await builderClient.buildHeaders();
|
|
17009
17010
|
headers.Origin = "https://app.gohighlevel.com";
|
|
17010
17011
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
17011
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
17012
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path12}`;
|
|
17012
17013
|
const options = { method, headers };
|
|
17013
17014
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
17014
17015
|
const response = await fetch(url, options);
|
|
17015
17016
|
if (!response.ok) {
|
|
17016
17017
|
const text2 = await response.text();
|
|
17017
|
-
throw new Error(`Funnel API ${response.status}: ${method} ${
|
|
17018
|
+
throw new Error(`Funnel API ${response.status}: ${method} ${path12}
|
|
17018
17019
|
${text2.slice(0, 300)}`);
|
|
17019
17020
|
}
|
|
17020
17021
|
const text = await response.text();
|
|
@@ -17723,6 +17724,157 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
|
|
|
17723
17724
|
// src/index.ts
|
|
17724
17725
|
init_credentials_store();
|
|
17725
17726
|
init_setup_tool();
|
|
17727
|
+
|
|
17728
|
+
// src/tools/skills.ts
|
|
17729
|
+
var import_zod57 = require("zod");
|
|
17730
|
+
|
|
17731
|
+
// src/skill-installer.ts
|
|
17732
|
+
var import_node_crypto2 = require("node:crypto");
|
|
17733
|
+
var fs6 = __toESM(require("node:fs"));
|
|
17734
|
+
var path6 = __toESM(require("node:path"));
|
|
17735
|
+
var os3 = __toESM(require("node:os"));
|
|
17736
|
+
function sha256(buf) {
|
|
17737
|
+
return (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
|
|
17738
|
+
}
|
|
17739
|
+
function bundledSkillsDir(baseDir) {
|
|
17740
|
+
const candidate = path6.resolve(baseDir, "..", "skills");
|
|
17741
|
+
try {
|
|
17742
|
+
return fs6.statSync(candidate).isDirectory() ? candidate : null;
|
|
17743
|
+
} catch {
|
|
17744
|
+
return null;
|
|
17745
|
+
}
|
|
17746
|
+
}
|
|
17747
|
+
function listFiles(root) {
|
|
17748
|
+
const out = [];
|
|
17749
|
+
const walk = (dir) => {
|
|
17750
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
17751
|
+
const full = path6.join(dir, entry.name);
|
|
17752
|
+
if (entry.isDirectory()) walk(full);
|
|
17753
|
+
else if (entry.isFile()) out.push(path6.relative(root, full));
|
|
17754
|
+
}
|
|
17755
|
+
};
|
|
17756
|
+
walk(root);
|
|
17757
|
+
return out.sort();
|
|
17758
|
+
}
|
|
17759
|
+
function readMarker(markerPath) {
|
|
17760
|
+
try {
|
|
17761
|
+
const parsed = JSON.parse(fs6.readFileSync(markerPath, "utf8"));
|
|
17762
|
+
return parsed && typeof parsed === "object" && parsed.files ? parsed : null;
|
|
17763
|
+
} catch {
|
|
17764
|
+
return null;
|
|
17765
|
+
}
|
|
17766
|
+
}
|
|
17767
|
+
function installBundledSkills(opts) {
|
|
17768
|
+
const result = {
|
|
17769
|
+
installed: [],
|
|
17770
|
+
updated: [],
|
|
17771
|
+
skippedUserModified: [],
|
|
17772
|
+
unchanged: [],
|
|
17773
|
+
errors: [],
|
|
17774
|
+
targetDir: opts.targetDir ?? path6.join(os3.homedir(), ".claude", "skills"),
|
|
17775
|
+
bundledDir: null
|
|
17776
|
+
};
|
|
17777
|
+
const bundled = bundledSkillsDir(opts.baseDir);
|
|
17778
|
+
result.bundledDir = bundled;
|
|
17779
|
+
if (!bundled) return result;
|
|
17780
|
+
try {
|
|
17781
|
+
fs6.mkdirSync(result.targetDir, { recursive: true });
|
|
17782
|
+
} catch (e) {
|
|
17783
|
+
result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
|
|
17784
|
+
return result;
|
|
17785
|
+
}
|
|
17786
|
+
const markerPath = path6.join(result.targetDir, ".ghl-command-skills.json");
|
|
17787
|
+
const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
|
|
17788
|
+
const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
|
|
17789
|
+
for (const rel of listFiles(bundled)) {
|
|
17790
|
+
try {
|
|
17791
|
+
const src = fs6.readFileSync(path6.join(bundled, rel));
|
|
17792
|
+
const dest = path6.join(result.targetDir, rel);
|
|
17793
|
+
const srcHash = sha256(src);
|
|
17794
|
+
let destBuf = null;
|
|
17795
|
+
try {
|
|
17796
|
+
destBuf = fs6.readFileSync(dest);
|
|
17797
|
+
} catch {
|
|
17798
|
+
destBuf = null;
|
|
17799
|
+
}
|
|
17800
|
+
if (destBuf === null) {
|
|
17801
|
+
fs6.mkdirSync(path6.dirname(dest), { recursive: true });
|
|
17802
|
+
fs6.writeFileSync(dest, src);
|
|
17803
|
+
newMarker.files[rel] = srcHash;
|
|
17804
|
+
result.installed.push(rel);
|
|
17805
|
+
continue;
|
|
17806
|
+
}
|
|
17807
|
+
const destHash = sha256(destBuf);
|
|
17808
|
+
if (destHash === srcHash) {
|
|
17809
|
+
newMarker.files[rel] = srcHash;
|
|
17810
|
+
result.unchanged.push(rel);
|
|
17811
|
+
continue;
|
|
17812
|
+
}
|
|
17813
|
+
const lastWritten = marker.files[rel];
|
|
17814
|
+
if (lastWritten && destHash === lastWritten) {
|
|
17815
|
+
fs6.writeFileSync(dest, src);
|
|
17816
|
+
newMarker.files[rel] = srcHash;
|
|
17817
|
+
result.updated.push(rel);
|
|
17818
|
+
} else {
|
|
17819
|
+
result.skippedUserModified.push(rel);
|
|
17820
|
+
}
|
|
17821
|
+
} catch (e) {
|
|
17822
|
+
result.errors.push(`${rel}: ${String(e)}`);
|
|
17823
|
+
}
|
|
17824
|
+
}
|
|
17825
|
+
try {
|
|
17826
|
+
fs6.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
|
|
17827
|
+
} catch (e) {
|
|
17828
|
+
result.errors.push(`marker write failed: ${String(e)}`);
|
|
17829
|
+
}
|
|
17830
|
+
return result;
|
|
17831
|
+
}
|
|
17832
|
+
function summarizeInstall(r) {
|
|
17833
|
+
if (!r.bundledDir) return "";
|
|
17834
|
+
const bits = [];
|
|
17835
|
+
if (r.installed.length) bits.push(`${r.installed.length} installed`);
|
|
17836
|
+
if (r.updated.length) bits.push(`${r.updated.length} updated`);
|
|
17837
|
+
if (r.skippedUserModified.length) bits.push(`${r.skippedUserModified.length} kept (your edits)`);
|
|
17838
|
+
if (r.errors.length) bits.push(`${r.errors.length} errors`);
|
|
17839
|
+
if (bits.length === 0) return "";
|
|
17840
|
+
return `Skills: ${bits.join(", ")} \u2192 ${r.targetDir}`;
|
|
17841
|
+
}
|
|
17842
|
+
|
|
17843
|
+
// src/tools/skills.ts
|
|
17844
|
+
function registerSkillsTool(server2, packageVersion, baseDir) {
|
|
17845
|
+
server2.tool(
|
|
17846
|
+
"install_skills",
|
|
17847
|
+
"Install (or repair) the guided skills bundled with GHL Command \u2014 currently the Blueprint skill (build a whole client account from one intake) \u2014 into your ~/.claude/skills/ so Claude can use them. Runs automatically on startup; call this to verify what is installed, or to re-install after deleting a skill. NEVER overwrites files you have edited (your version is kept and reported). Restart Claude after install for new skills to load.",
|
|
17848
|
+
{
|
|
17849
|
+
targetDir: import_zod57.z.string().optional().describe("Override the install directory. Default: ~/.claude/skills")
|
|
17850
|
+
},
|
|
17851
|
+
async ({ targetDir }) => {
|
|
17852
|
+
try {
|
|
17853
|
+
const result = installBundledSkills({ packageVersion, baseDir, targetDir });
|
|
17854
|
+
if (!result.bundledDir) {
|
|
17855
|
+
return jsonResponse({
|
|
17856
|
+
ok: false,
|
|
17857
|
+
note: "No bundled skills directory found in this install (development checkout without skills/, or a pre-3.55.0 package)."
|
|
17858
|
+
});
|
|
17859
|
+
}
|
|
17860
|
+
return jsonResponse({
|
|
17861
|
+
ok: result.errors.length === 0,
|
|
17862
|
+
targetDir: result.targetDir,
|
|
17863
|
+
installed: result.installed,
|
|
17864
|
+
updated: result.updated,
|
|
17865
|
+
keptYourEdits: result.skippedUserModified,
|
|
17866
|
+
unchanged: result.unchanged.length,
|
|
17867
|
+
errors: result.errors,
|
|
17868
|
+
next: result.installed.length > 0 || result.updated.length > 0 ? "Fully restart Claude (quit and reopen) so the new/updated skills load." : "Everything already current."
|
|
17869
|
+
});
|
|
17870
|
+
} catch (error) {
|
|
17871
|
+
return errorResponse(error);
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17874
|
+
);
|
|
17875
|
+
}
|
|
17876
|
+
|
|
17877
|
+
// src/index.ts
|
|
17726
17878
|
init_attestation();
|
|
17727
17879
|
|
|
17728
17880
|
// src/tools/meta.ts
|
|
@@ -17756,8 +17908,8 @@ function registerMetaTools(server2, installedVersion) {
|
|
|
17756
17908
|
|
|
17757
17909
|
// src/cli.ts
|
|
17758
17910
|
var import_node_util = require("node:util");
|
|
17759
|
-
var
|
|
17760
|
-
var
|
|
17911
|
+
var fs7 = __toESM(require("fs"));
|
|
17912
|
+
var path7 = __toESM(require("path"));
|
|
17761
17913
|
var import_crypto2 = require("crypto");
|
|
17762
17914
|
init_ghl_client();
|
|
17763
17915
|
init_token_registry();
|
|
@@ -17800,9 +17952,9 @@ function errLine(msg2) {
|
|
|
17800
17952
|
function preflightWritable() {
|
|
17801
17953
|
try {
|
|
17802
17954
|
const dir = ensureAppDataDir();
|
|
17803
|
-
const probe =
|
|
17804
|
-
|
|
17805
|
-
|
|
17955
|
+
const probe = path7.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
|
|
17956
|
+
fs7.writeFileSync(probe, "ok");
|
|
17957
|
+
fs7.unlinkSync(probe);
|
|
17806
17958
|
return true;
|
|
17807
17959
|
} catch (error) {
|
|
17808
17960
|
errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -18052,7 +18204,7 @@ var bundledPkg = require_package();
|
|
|
18052
18204
|
var pkg = (() => {
|
|
18053
18205
|
try {
|
|
18054
18206
|
const onDisk = JSON.parse(
|
|
18055
|
-
|
|
18207
|
+
fs11.readFileSync(path11.resolve(__dirname, "..", "package.json"), "utf8")
|
|
18056
18208
|
);
|
|
18057
18209
|
if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
|
|
18058
18210
|
return { version: onDisk.version };
|
|
@@ -18064,7 +18216,7 @@ var pkg = (() => {
|
|
|
18064
18216
|
dotenv2.config();
|
|
18065
18217
|
{
|
|
18066
18218
|
const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
18067
|
-
if (configDirOverride && !
|
|
18219
|
+
if (configDirOverride && !path11.isAbsolute(configDirOverride)) {
|
|
18068
18220
|
process.stderr.write(
|
|
18069
18221
|
`[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.
|
|
18070
18222
|
`
|
|
@@ -18077,20 +18229,20 @@ process.on("unhandledRejection", (reason) => {
|
|
|
18077
18229
|
`);
|
|
18078
18230
|
});
|
|
18079
18231
|
function hardenSecretFilePerms() {
|
|
18080
|
-
const repoDir =
|
|
18232
|
+
const repoDir = path11.resolve(__dirname, "..");
|
|
18081
18233
|
const candidates = [
|
|
18082
|
-
{ file:
|
|
18234
|
+
{ file: path11.join(repoDir, "start-mcp.sh"), mode: 448 },
|
|
18083
18235
|
// Legacy registry location (pre-migration); new location lives in app-data.
|
|
18084
|
-
{ file:
|
|
18236
|
+
{ file: path11.join(repoDir, ".ghl-tokens.json"), mode: 384 },
|
|
18085
18237
|
{ file: tokenRegistryPath(), mode: 384 }
|
|
18086
18238
|
];
|
|
18087
18239
|
for (const { file, mode } of candidates) {
|
|
18088
18240
|
let current;
|
|
18089
18241
|
try {
|
|
18090
|
-
if (!
|
|
18091
|
-
current =
|
|
18242
|
+
if (!fs11.existsSync(file)) continue;
|
|
18243
|
+
current = fs11.statSync(file).mode & 511;
|
|
18092
18244
|
if (current !== mode) {
|
|
18093
|
-
|
|
18245
|
+
fs11.chmodSync(file, mode);
|
|
18094
18246
|
}
|
|
18095
18247
|
} catch (error) {
|
|
18096
18248
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -18272,6 +18424,16 @@ async function resolveAccessAndRegister() {
|
|
|
18272
18424
|
registerEnableWorkflowBuilderTool(server);
|
|
18273
18425
|
registerFirebaseCaptureScriptTool(server);
|
|
18274
18426
|
registerInteractiveCaptureTool(server);
|
|
18427
|
+
registerSkillsTool(server, pkg.version, __dirname);
|
|
18428
|
+
try {
|
|
18429
|
+
const skillsResult = installBundledSkills({ packageVersion: pkg.version, baseDir: __dirname });
|
|
18430
|
+
const line = summarizeInstall(skillsResult);
|
|
18431
|
+
if (line) process.stderr.write(`[ghl-mcp] ${line}
|
|
18432
|
+
`);
|
|
18433
|
+
} catch (e) {
|
|
18434
|
+
process.stderr.write(`[ghl-mcp] Skills install skipped: ${String(e).slice(0, 120)}
|
|
18435
|
+
`);
|
|
18436
|
+
}
|
|
18275
18437
|
if (fileCreds && !process.env.GHL_API_KEY) {
|
|
18276
18438
|
process.stderr.write(`[ghl-mcp] Loaded credentials from ${credentialsPath()}
|
|
18277
18439
|
`);
|