@elitedcs/ghl-mcp 3.58.0 → 3.59.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 +36 -0
- package/README.md +20 -7
- package/dist/index.js +564 -154
- package/package.json +3 -2
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(path14, params) {
|
|
118
|
+
const url = new URL(path14, 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, path14, options = {}, attempt = 0) {
|
|
129
|
+
const url = this.buildUrl(path14, 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} ${path14}`);
|
|
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} ${path14}, 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, path14, 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} ${path14}, 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, path14, 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} ${path14}
|
|
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(path14, options) {
|
|
187
|
+
return this.request("GET", path14, options);
|
|
188
188
|
}
|
|
189
|
-
async post(
|
|
190
|
-
return this.request("POST",
|
|
189
|
+
async post(path14, options) {
|
|
190
|
+
return this.request("POST", path14, options);
|
|
191
191
|
}
|
|
192
|
-
async put(
|
|
193
|
-
return this.request("PUT",
|
|
192
|
+
async put(path14, options) {
|
|
193
|
+
return this.request("PUT", path14, options);
|
|
194
194
|
}
|
|
195
|
-
async patch(
|
|
196
|
-
return this.request("PATCH",
|
|
195
|
+
async patch(path14, options) {
|
|
196
|
+
return this.request("PATCH", path14, options);
|
|
197
197
|
}
|
|
198
|
-
async delete(
|
|
199
|
-
return this.request("DELETE",
|
|
198
|
+
async delete(path14, options) {
|
|
199
|
+
return this.request("DELETE", path14, options);
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Helper: resolves locationId from args or falls back to default
|
|
@@ -1270,9 +1270,9 @@ function planFromPayload(payload) {
|
|
|
1270
1270
|
return payload?.plan === "command-os" ? "command-os" : "mcp";
|
|
1271
1271
|
}
|
|
1272
1272
|
function legacyDeviceFingerprint() {
|
|
1273
|
-
const
|
|
1273
|
+
const os7 = require("node:os");
|
|
1274
1274
|
const crypto5 = require("node:crypto");
|
|
1275
|
-
const raw = `${
|
|
1275
|
+
const raw = `${os7.hostname()}:${os7.userInfo().username}:${os7.platform()}:${os7.arch()}`;
|
|
1276
1276
|
return crypto5.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
1277
1277
|
}
|
|
1278
1278
|
function deviceFingerprint(opts) {
|
|
@@ -1508,7 +1508,7 @@ function registerSetupTool(server2, pkgVersion) {
|
|
|
1508
1508
|
if (pkgVersion) setupPkgVersion = pkgVersion;
|
|
1509
1509
|
server2.tool(
|
|
1510
1510
|
"setup_ghl_mcp",
|
|
1511
|
-
"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 233 tools (179 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder).",
|
|
1511
|
+
"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 233 tools (179 if you skip the optional Firebase fields; add Firebase later with enable_workflow_builder). No license yet? Paid ($97/mo) at https://ghlcommand.com \u2014 or a FREE read-only key, instantly, at https://ghlcommand.com/free.",
|
|
1512
1512
|
{
|
|
1513
1513
|
email: import_zod42.z.string().email().describe("Email used at purchase."),
|
|
1514
1514
|
license_key: import_zod42.z.string().min(20).describe("License key from your purchase email."),
|
|
@@ -1531,7 +1531,7 @@ function registerSetupTool(server2, pkgVersion) {
|
|
|
1531
1531
|
emit("setup_failed", { pkgVersion: setupPkgVersion, reasonCode: LICENSE_REASON_MAP[lic.reason] });
|
|
1532
1532
|
return { content: [{ type: "text", text: `License check failed: ${lic.error}
|
|
1533
1533
|
|
|
1534
|
-
Purchase a license at https://
|
|
1534
|
+
Purchase a license at https://ghlcommand.com \u2014 or get a FREE read-only key instantly at https://ghlcommand.com/free. Stuck? Reply to any email from us or write support@ghlcommand.com.` }], isError: true };
|
|
1535
1535
|
}
|
|
1536
1536
|
const ghl = await validateGhl(args.ghl_api_key, args.ghl_location_id);
|
|
1537
1537
|
if (!ghl.ok) {
|
|
@@ -2390,12 +2390,409 @@ var init_question_set = __esm({
|
|
|
2390
2390
|
}
|
|
2391
2391
|
});
|
|
2392
2392
|
|
|
2393
|
+
// src/config-installer.ts
|
|
2394
|
+
var config_installer_exports = {};
|
|
2395
|
+
__export(config_installer_exports, {
|
|
2396
|
+
EXIT_ABORTED: () => EXIT_ABORTED,
|
|
2397
|
+
EXIT_OK: () => EXIT_OK,
|
|
2398
|
+
EXIT_REFUSED: () => EXIT_REFUSED,
|
|
2399
|
+
EXIT_USAGE: () => EXIT_USAGE,
|
|
2400
|
+
atomicReplace: () => atomicReplace,
|
|
2401
|
+
baselineOf: () => baselineOf,
|
|
2402
|
+
candidateConfigPaths: () => candidateConfigPaths,
|
|
2403
|
+
chooseBackupPath: () => chooseBackupPath,
|
|
2404
|
+
resolveConfigPath: () => resolveConfigPath,
|
|
2405
|
+
resolveSymlinkPolicy: () => resolveSymlinkPolicy,
|
|
2406
|
+
runInstall: () => runInstall
|
|
2407
|
+
});
|
|
2408
|
+
function defaultIO() {
|
|
2409
|
+
return {
|
|
2410
|
+
out: (l) => process.stdout.write(l + "\n"),
|
|
2411
|
+
err: (l) => process.stderr.write(l + "\n"),
|
|
2412
|
+
rename: (from, to) => fs9.renameSync(from, to),
|
|
2413
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
function candidateConfigPaths(opts) {
|
|
2417
|
+
const platform2 = opts?.platform ?? process.platform;
|
|
2418
|
+
const home = opts?.home ?? os4.homedir();
|
|
2419
|
+
const file = "claude_desktop_config.json";
|
|
2420
|
+
const candidates = [];
|
|
2421
|
+
if (platform2 === "darwin") {
|
|
2422
|
+
candidates.push(path8.join(home, "Library", "Application Support", "Claude", file));
|
|
2423
|
+
} else if (platform2 === "win32") {
|
|
2424
|
+
const appData = opts?.appData ?? process.env.APPDATA ?? path8.join(home, "AppData", "Roaming");
|
|
2425
|
+
candidates.push(path8.join(appData, "Claude", file));
|
|
2426
|
+
const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path8.join(home, "AppData", "Local");
|
|
2427
|
+
const packagesDir = path8.join(localAppData, "Packages");
|
|
2428
|
+
try {
|
|
2429
|
+
for (const entry of fs9.readdirSync(packagesDir)) {
|
|
2430
|
+
if (entry.startsWith("Claude_")) {
|
|
2431
|
+
candidates.push(path8.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file));
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
} catch {
|
|
2435
|
+
}
|
|
2436
|
+
} else {
|
|
2437
|
+
candidates.push(path8.join(home, ".config", "Claude", file));
|
|
2438
|
+
}
|
|
2439
|
+
return candidates;
|
|
2440
|
+
}
|
|
2441
|
+
function resolveConfigPath(explicitPath) {
|
|
2442
|
+
if (explicitPath) {
|
|
2443
|
+
const p = path8.resolve(explicitPath);
|
|
2444
|
+
return { configPath: p, exists: fs9.existsSync(p) };
|
|
2445
|
+
}
|
|
2446
|
+
const candidates = candidateConfigPaths();
|
|
2447
|
+
const existing = candidates.filter((c) => fs9.existsSync(c));
|
|
2448
|
+
if (existing.length > 1) {
|
|
2449
|
+
throw new InstallStop(
|
|
2450
|
+
EXIT_REFUSED,
|
|
2451
|
+
[
|
|
2452
|
+
"More than one Claude settings file was found, and picking the wrong one would break your setup:",
|
|
2453
|
+
...existing.map((p) => ` ${p}`),
|
|
2454
|
+
"Run the command again with --path and the one Claude actually uses. Nothing was changed."
|
|
2455
|
+
].join("\n")
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
if (existing.length === 1) return { configPath: existing[0], exists: true };
|
|
2459
|
+
return { configPath: candidates[0], exists: false };
|
|
2460
|
+
}
|
|
2461
|
+
function resolveSymlinkPolicy(configPath, explicitPath, home = os4.homedir()) {
|
|
2462
|
+
let st;
|
|
2463
|
+
try {
|
|
2464
|
+
st = fs9.lstatSync(configPath);
|
|
2465
|
+
} catch {
|
|
2466
|
+
return configPath;
|
|
2467
|
+
}
|
|
2468
|
+
if (!st.isSymbolicLink()) return configPath;
|
|
2469
|
+
let real;
|
|
2470
|
+
try {
|
|
2471
|
+
real = fs9.realpathSync(configPath);
|
|
2472
|
+
} catch {
|
|
2473
|
+
throw new InstallStop(
|
|
2474
|
+
EXIT_REFUSED,
|
|
2475
|
+
`${configPath} is a link that points to a file that does not exist. Fix or remove the link, or run again with --path to a real file. Nothing was changed.`
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
const rel = path8.relative(path8.resolve(home), real);
|
|
2479
|
+
const outsideHome = rel.startsWith("..") || path8.isAbsolute(rel);
|
|
2480
|
+
if (outsideHome && !explicitPath) {
|
|
2481
|
+
throw new InstallStop(
|
|
2482
|
+
EXIT_REFUSED,
|
|
2483
|
+
`${configPath} is a link pointing outside your home folder (to ${real}). If that is intentional, run again with --path pointing at it directly. Nothing was changed.`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
return real;
|
|
2487
|
+
}
|
|
2488
|
+
function readConfigBytes(configPath) {
|
|
2489
|
+
const bytes = fs9.readFileSync(configPath);
|
|
2490
|
+
const stat = fs9.statSync(configPath);
|
|
2491
|
+
if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 254 && bytes[1] === 255)) {
|
|
2492
|
+
throw new InstallStop(
|
|
2493
|
+
EXIT_REFUSED,
|
|
2494
|
+
"This settings file is saved in an encoding this command doesn't edit (UTF-16). Nothing was changed. Reply to your setup email and we'll sort it out."
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
const hadBom = bytes.length >= 3 && bytes.subarray(0, 3).equals(BOM_UTF8);
|
|
2498
|
+
const text = (hadBom ? bytes.subarray(3) : bytes).toString("utf8");
|
|
2499
|
+
return { text, hadBom, bytes, stat };
|
|
2500
|
+
}
|
|
2501
|
+
function isPlainObject(v) {
|
|
2502
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2503
|
+
}
|
|
2504
|
+
function wrongShapeStop() {
|
|
2505
|
+
return new InstallStop(
|
|
2506
|
+
EXIT_REFUSED,
|
|
2507
|
+
"This settings file doesn't have the layout Claude uses, so this command won't rewrite it. Nothing was changed. Reply to your setup email and we'll sort it out."
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
function parseTiered(text) {
|
|
2511
|
+
if (text.trim() === "") return { tier: 1, root: {} };
|
|
2512
|
+
try {
|
|
2513
|
+
const strict = JSON.parse(text);
|
|
2514
|
+
if (!isPlainObject(strict)) throw wrongShapeStop();
|
|
2515
|
+
return { tier: 1, root: strict };
|
|
2516
|
+
} catch (e) {
|
|
2517
|
+
if (e instanceof InstallStop) throw e;
|
|
2518
|
+
}
|
|
2519
|
+
try {
|
|
2520
|
+
const tolerant = import_json5.default.parse(text);
|
|
2521
|
+
if (!isPlainObject(tolerant)) throw wrongShapeStop();
|
|
2522
|
+
return { tier: 2, root: tolerant };
|
|
2523
|
+
} catch (e) {
|
|
2524
|
+
if (e instanceof InstallStop) throw e;
|
|
2525
|
+
}
|
|
2526
|
+
return { tier: 3, root: null };
|
|
2527
|
+
}
|
|
2528
|
+
function entriesEqual(a) {
|
|
2529
|
+
if (!isPlainObject(a)) return false;
|
|
2530
|
+
return a.command === DESIRED_ENTRY.command && Array.isArray(a.args) && a.args.length === DESIRED_ENTRY.args.length && a.args.every((v, i) => v === DESIRED_ENTRY.args[i]);
|
|
2531
|
+
}
|
|
2532
|
+
function mergeGhlEntry(root, force) {
|
|
2533
|
+
const existing = root.mcpServers;
|
|
2534
|
+
if (existing !== void 0 && !isPlainObject(existing)) {
|
|
2535
|
+
throw wrongShapeStop();
|
|
2536
|
+
}
|
|
2537
|
+
const servers = isPlainObject(existing) ? existing : {};
|
|
2538
|
+
const keptServers = Object.keys(servers).filter((k) => k !== SERVER_KEY);
|
|
2539
|
+
let action;
|
|
2540
|
+
if (SERVER_KEY in servers) {
|
|
2541
|
+
if (entriesEqual(servers[SERVER_KEY])) {
|
|
2542
|
+
action = "identical";
|
|
2543
|
+
} else if (!force) {
|
|
2544
|
+
throw new InstallStop(
|
|
2545
|
+
EXIT_REFUSED,
|
|
2546
|
+
"A different GHL Command entry is already in this file. Run again with --force to replace it, or leave it as is. Nothing was changed."
|
|
2547
|
+
);
|
|
2548
|
+
} else {
|
|
2549
|
+
action = "replaced";
|
|
2550
|
+
}
|
|
2551
|
+
} else {
|
|
2552
|
+
action = "added";
|
|
2553
|
+
}
|
|
2554
|
+
servers[SERVER_KEY] = { command: DESIRED_ENTRY.command, args: [...DESIRED_ENTRY.args] };
|
|
2555
|
+
root.mcpServers = servers;
|
|
2556
|
+
return { merged: root, keptServers, action };
|
|
2557
|
+
}
|
|
2558
|
+
function backupStamp(now) {
|
|
2559
|
+
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
2560
|
+
return `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}-${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
|
|
2561
|
+
}
|
|
2562
|
+
function chooseBackupPath(configPath, now = /* @__PURE__ */ new Date()) {
|
|
2563
|
+
const base = `${configPath}.bak-${backupStamp(now)}`;
|
|
2564
|
+
if (!fs9.existsSync(base)) return base;
|
|
2565
|
+
for (let i = 2; ; i++) {
|
|
2566
|
+
const candidate = `${base}-${i}`;
|
|
2567
|
+
if (!fs9.existsSync(candidate)) return candidate;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
function writeVerifiedBackup(configPath, originalBytes) {
|
|
2571
|
+
const backupPath = chooseBackupPath(configPath);
|
|
2572
|
+
try {
|
|
2573
|
+
fs9.writeFileSync(backupPath, originalBytes);
|
|
2574
|
+
const readBack = fs9.readFileSync(backupPath);
|
|
2575
|
+
if (!readBack.equals(originalBytes)) {
|
|
2576
|
+
throw new Error("backup read-back did not match");
|
|
2577
|
+
}
|
|
2578
|
+
} catch (e) {
|
|
2579
|
+
try {
|
|
2580
|
+
fs9.rmSync(backupPath, { force: true });
|
|
2581
|
+
} catch {
|
|
2582
|
+
}
|
|
2583
|
+
throw new InstallStop(
|
|
2584
|
+
EXIT_ABORTED,
|
|
2585
|
+
`A safety copy of your settings could not be saved, so nothing was changed. (${e instanceof Error ? e.message : String(e)})`
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
return backupPath;
|
|
2589
|
+
}
|
|
2590
|
+
function sha2562(bytes) {
|
|
2591
|
+
return (0, import_node_crypto3.createHash)("sha256").update(bytes).digest("hex");
|
|
2592
|
+
}
|
|
2593
|
+
function baselineOf(bytes, stat) {
|
|
2594
|
+
return { size: stat.size, mtimeMs: stat.mtimeMs, hash: sha2562(bytes) };
|
|
2595
|
+
}
|
|
2596
|
+
function assertNotStale(configPath, baseline) {
|
|
2597
|
+
let ok = false;
|
|
2598
|
+
try {
|
|
2599
|
+
const st = fs9.statSync(configPath);
|
|
2600
|
+
if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
|
|
2601
|
+
ok = sha2562(fs9.readFileSync(configPath)) === baseline.hash;
|
|
2602
|
+
} else {
|
|
2603
|
+
ok = false;
|
|
2604
|
+
}
|
|
2605
|
+
} catch {
|
|
2606
|
+
ok = false;
|
|
2607
|
+
}
|
|
2608
|
+
if (!ok) {
|
|
2609
|
+
throw new InstallStop(
|
|
2610
|
+
EXIT_ABORTED,
|
|
2611
|
+
"Your settings file changed while this command was running, so it stopped without touching it. Run the command again."
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
async function atomicReplace(opts) {
|
|
2616
|
+
const dir = path8.dirname(opts.configPath);
|
|
2617
|
+
const tempPath = path8.join(dir, `.${path8.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`);
|
|
2618
|
+
const fd = fs9.openSync(tempPath, "w");
|
|
2619
|
+
try {
|
|
2620
|
+
fs9.writeFileSync(fd, opts.newBytes);
|
|
2621
|
+
fs9.fsyncSync(fd);
|
|
2622
|
+
} finally {
|
|
2623
|
+
fs9.closeSync(fd);
|
|
2624
|
+
}
|
|
2625
|
+
try {
|
|
2626
|
+
for (let attempt = 0; ; attempt++) {
|
|
2627
|
+
if (opts.baseline) assertNotStale(opts.configPath, opts.baseline);
|
|
2628
|
+
try {
|
|
2629
|
+
opts.io.rename(tempPath, opts.configPath);
|
|
2630
|
+
break;
|
|
2631
|
+
} catch (e) {
|
|
2632
|
+
const code = e.code ?? "";
|
|
2633
|
+
const retryable = LOCK_ERROR_CODES.has(code) && attempt < RENAME_RETRY_DELAYS_MS.length;
|
|
2634
|
+
if (!retryable) {
|
|
2635
|
+
if (LOCK_ERROR_CODES.has(code)) {
|
|
2636
|
+
throw new InstallStop(
|
|
2637
|
+
EXIT_ABORTED,
|
|
2638
|
+
"Claude is holding this file. Quit Claude completely (right-click the Claude icon by your clock and choose Quit \u2014 on Mac press Cmd+Q), then run this command again. Nothing was changed."
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
throw e;
|
|
2642
|
+
}
|
|
2643
|
+
await opts.io.sleep(RENAME_RETRY_DELAYS_MS[attempt]);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
} catch (e) {
|
|
2647
|
+
try {
|
|
2648
|
+
fs9.rmSync(tempPath, { force: true });
|
|
2649
|
+
} catch {
|
|
2650
|
+
}
|
|
2651
|
+
throw e;
|
|
2652
|
+
}
|
|
2653
|
+
try {
|
|
2654
|
+
const dirFd = fs9.openSync(dir, "r");
|
|
2655
|
+
try {
|
|
2656
|
+
fs9.fsyncSync(dirFd);
|
|
2657
|
+
} finally {
|
|
2658
|
+
fs9.closeSync(dirFd);
|
|
2659
|
+
}
|
|
2660
|
+
} catch {
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
function successReport(io, args) {
|
|
2664
|
+
io.out("Added GHL Command to Claude Desktop.");
|
|
2665
|
+
io.out("");
|
|
2666
|
+
io.out(` Settings file: ${args.configPath}`);
|
|
2667
|
+
if (args.backupPath) io.out(` Backup saved: ${args.backupPath}`);
|
|
2668
|
+
if (args.tier !== 3 && args.keptServers.length > 0) {
|
|
2669
|
+
io.out(` Kept your other tools: ${args.keptServers.join(", ")}`);
|
|
2670
|
+
}
|
|
2671
|
+
if (args.tier === 2) {
|
|
2672
|
+
io.out("");
|
|
2673
|
+
io.out(`Your settings file was rewritten in a cleaner format. The original is saved at ${args.backupPath}.`);
|
|
2674
|
+
}
|
|
2675
|
+
if (args.tier === 3 && args.hadOriginal) {
|
|
2676
|
+
io.out("");
|
|
2677
|
+
io.out(
|
|
2678
|
+
`GHL Command is ready. If you had other tools connected before, reply to your setup email and we'll reconnect them \u2014 your previous settings are saved at ${args.backupPath}.`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
io.out("");
|
|
2682
|
+
io.out("NEXT STEP \u2014 quit Claude completely, not just the window:");
|
|
2683
|
+
io.out(" Windows: right-click the Claude icon by the clock, choose Quit");
|
|
2684
|
+
io.out(" Mac: Cmd+Q");
|
|
2685
|
+
io.out("Then reopen Claude and run setup with your license key.");
|
|
2686
|
+
}
|
|
2687
|
+
async function runInstall(argv, ioOverride) {
|
|
2688
|
+
const io = { ...defaultIO(), ...ioOverride };
|
|
2689
|
+
let flags;
|
|
2690
|
+
try {
|
|
2691
|
+
flags = (0, import_node_util.parseArgs)({
|
|
2692
|
+
args: argv,
|
|
2693
|
+
options: {
|
|
2694
|
+
"dry-run": { type: "boolean" },
|
|
2695
|
+
"print-only": { type: "boolean" },
|
|
2696
|
+
path: { type: "string" },
|
|
2697
|
+
force: { type: "boolean" }
|
|
2698
|
+
},
|
|
2699
|
+
strict: true,
|
|
2700
|
+
allowPositionals: false
|
|
2701
|
+
}).values;
|
|
2702
|
+
} catch (e) {
|
|
2703
|
+
io.err(e instanceof Error ? e.message : String(e));
|
|
2704
|
+
io.err("Usage: ghl-mcp cli install [--dry-run] [--print-only] [--path <file>] [--force]");
|
|
2705
|
+
return EXIT_USAGE;
|
|
2706
|
+
}
|
|
2707
|
+
try {
|
|
2708
|
+
const resolved2 = resolveConfigPath(flags.path);
|
|
2709
|
+
const configPath = resolveSymlinkPolicy(resolved2.configPath, flags.path !== void 0);
|
|
2710
|
+
const exists = fs9.existsSync(configPath);
|
|
2711
|
+
let read = null;
|
|
2712
|
+
let outcome;
|
|
2713
|
+
if (exists) {
|
|
2714
|
+
read = readConfigBytes(configPath);
|
|
2715
|
+
outcome = parseTiered(read.text);
|
|
2716
|
+
} else {
|
|
2717
|
+
outcome = { tier: 1, root: {} };
|
|
2718
|
+
}
|
|
2719
|
+
const { merged, keptServers, action } = outcome.tier === 3 ? { merged: { mcpServers: { [SERVER_KEY]: { command: DESIRED_ENTRY.command, args: [...DESIRED_ENTRY.args] } } }, keptServers: [], action: "added" } : mergeGhlEntry(outcome.root, flags.force === true);
|
|
2720
|
+
if (action === "identical") {
|
|
2721
|
+
io.out("GHL Command is already set up in Claude Desktop \u2014 nothing to do.");
|
|
2722
|
+
return EXIT_OK;
|
|
2723
|
+
}
|
|
2724
|
+
const newText = JSON.stringify(merged, null, 2) + "\n";
|
|
2725
|
+
const newBytes = read?.hadBom ? Buffer.concat([BOM_UTF8, Buffer.from(newText, "utf8")]) : Buffer.from(newText, "utf8");
|
|
2726
|
+
if (flags["print-only"]) {
|
|
2727
|
+
io.out(newText.trimEnd());
|
|
2728
|
+
return EXIT_OK;
|
|
2729
|
+
}
|
|
2730
|
+
if (flags["dry-run"]) {
|
|
2731
|
+
io.out(`Would edit: ${configPath}${exists ? "" : " (new file)"}`);
|
|
2732
|
+
io.out(`Would ${action === "replaced" ? "replace" : "add"} the "${SERVER_KEY}" entry.`);
|
|
2733
|
+
if (keptServers.length > 0) io.out(`Would keep: ${keptServers.join(", ")}`);
|
|
2734
|
+
if (exists) io.out(`Would back up: ${chooseBackupPath(configPath)}`);
|
|
2735
|
+
if (outcome.tier === 2) io.out("Would rewrite the file in a cleaner format (original kept as the backup).");
|
|
2736
|
+
if (outcome.tier === 3) io.out("Would write a fresh settings file (original kept as the backup).");
|
|
2737
|
+
return EXIT_OK;
|
|
2738
|
+
}
|
|
2739
|
+
const backupPath = exists && read ? writeVerifiedBackup(configPath, read.bytes) : null;
|
|
2740
|
+
if (!exists) fs9.mkdirSync(path8.dirname(configPath), { recursive: true });
|
|
2741
|
+
await atomicReplace({
|
|
2742
|
+
configPath,
|
|
2743
|
+
newBytes,
|
|
2744
|
+
baseline: exists && read ? baselineOf(read.bytes, read.stat) : null,
|
|
2745
|
+
io
|
|
2746
|
+
});
|
|
2747
|
+
successReport(io, { configPath, backupPath, keptServers, tier: outcome.tier, hadOriginal: exists });
|
|
2748
|
+
return EXIT_OK;
|
|
2749
|
+
} catch (e) {
|
|
2750
|
+
if (e instanceof InstallStop) {
|
|
2751
|
+
io.err(e.message);
|
|
2752
|
+
return e.exitCode;
|
|
2753
|
+
}
|
|
2754
|
+
io.err(`Something went wrong and nothing was changed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2755
|
+
return EXIT_ABORTED;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
var fs9, os4, path8, import_node_crypto3, import_node_util, import_json5, SERVER_KEY, DESIRED_ENTRY, EXIT_OK, EXIT_USAGE, EXIT_REFUSED, EXIT_ABORTED, RENAME_RETRY_DELAYS_MS, LOCK_ERROR_CODES, BOM_UTF8, InstallStop;
|
|
2759
|
+
var init_config_installer = __esm({
|
|
2760
|
+
"src/config-installer.ts"() {
|
|
2761
|
+
"use strict";
|
|
2762
|
+
fs9 = __toESM(require("node:fs"));
|
|
2763
|
+
os4 = __toESM(require("node:os"));
|
|
2764
|
+
path8 = __toESM(require("node:path"));
|
|
2765
|
+
import_node_crypto3 = require("node:crypto");
|
|
2766
|
+
import_node_util = require("node:util");
|
|
2767
|
+
import_json5 = __toESM(require("json5"));
|
|
2768
|
+
SERVER_KEY = "ghl";
|
|
2769
|
+
DESIRED_ENTRY = Object.freeze({
|
|
2770
|
+
command: "npx",
|
|
2771
|
+
args: Object.freeze(["-y", "@elitedcs/ghl-mcp@latest"])
|
|
2772
|
+
});
|
|
2773
|
+
EXIT_OK = 0;
|
|
2774
|
+
EXIT_USAGE = 2;
|
|
2775
|
+
EXIT_REFUSED = 3;
|
|
2776
|
+
EXIT_ABORTED = 4;
|
|
2777
|
+
RENAME_RETRY_DELAYS_MS = [200, 500, 1e3];
|
|
2778
|
+
LOCK_ERROR_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
2779
|
+
BOM_UTF8 = Buffer.from([239, 187, 191]);
|
|
2780
|
+
InstallStop = class extends Error {
|
|
2781
|
+
constructor(exitCode, message) {
|
|
2782
|
+
super(message);
|
|
2783
|
+
this.exitCode = exitCode;
|
|
2784
|
+
}
|
|
2785
|
+
exitCode;
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2393
2790
|
// package.json
|
|
2394
2791
|
var require_package = __commonJS({
|
|
2395
2792
|
"package.json"(exports2, module2) {
|
|
2396
2793
|
module2.exports = {
|
|
2397
2794
|
name: "@elitedcs/ghl-mcp",
|
|
2398
|
-
version: "3.
|
|
2795
|
+
version: "3.59.0",
|
|
2399
2796
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
2400
2797
|
description: "GoHighLevel MCP Server for Claude. 233 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.",
|
|
2401
2798
|
main: "dist/index.js",
|
|
@@ -2458,6 +2855,7 @@ var require_package = __commonJS({
|
|
|
2458
2855
|
dependencies: {
|
|
2459
2856
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
2460
2857
|
dotenv: "^16.5.0",
|
|
2858
|
+
json5: "^2.2.3",
|
|
2461
2859
|
"playwright-core": "^1.61.1",
|
|
2462
2860
|
zod: "^3.24.4"
|
|
2463
2861
|
},
|
|
@@ -2485,7 +2883,7 @@ function launchCommand(port = 7300) {
|
|
|
2485
2883
|
return `npx -y @elitedcs/ghl-mcp@latest dashboard --port=${port}`;
|
|
2486
2884
|
}
|
|
2487
2885
|
function installingEntry() {
|
|
2488
|
-
return
|
|
2886
|
+
return path10.join(__dirname, "index.js");
|
|
2489
2887
|
}
|
|
2490
2888
|
function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
2491
2889
|
const cmd = launchCommand(port);
|
|
@@ -2493,12 +2891,12 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2493
2891
|
const systemApps = "/Applications";
|
|
2494
2892
|
let base = home;
|
|
2495
2893
|
try {
|
|
2496
|
-
|
|
2894
|
+
fs11.accessSync(systemApps, fs11.constants.W_OK);
|
|
2497
2895
|
base = "";
|
|
2498
2896
|
} catch {
|
|
2499
2897
|
}
|
|
2500
|
-
const appDir = base ?
|
|
2501
|
-
const macOSDir =
|
|
2898
|
+
const appDir = base ? path10.join(home, "Applications", "Command OS.app") : path10.join(systemApps, "Command OS.app");
|
|
2899
|
+
const macOSDir = path10.join(appDir, "Contents", "MacOS");
|
|
2502
2900
|
const script = [
|
|
2503
2901
|
"#!/bin/bash",
|
|
2504
2902
|
"# GHL Command \u2014 Command OS launcher (regenerate: ghl-mcp install-launcher)",
|
|
@@ -2527,13 +2925,13 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2527
2925
|
targetPath: appDir,
|
|
2528
2926
|
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",
|
|
2529
2927
|
files: [
|
|
2530
|
-
{ path:
|
|
2531
|
-
{ path:
|
|
2928
|
+
{ path: path10.join(macOSDir, "command-os"), contents: script, executable: true },
|
|
2929
|
+
{ path: path10.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
|
|
2532
2930
|
]
|
|
2533
2931
|
};
|
|
2534
2932
|
}
|
|
2535
2933
|
if (platform2 === "win32") {
|
|
2536
|
-
const target2 =
|
|
2934
|
+
const target2 = path10.join(home, "Desktop", "Command OS.cmd");
|
|
2537
2935
|
return {
|
|
2538
2936
|
platform: platform2,
|
|
2539
2937
|
targetPath: target2,
|
|
@@ -2546,7 +2944,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2546
2944
|
].join("\r\n"), executable: false }]
|
|
2547
2945
|
};
|
|
2548
2946
|
}
|
|
2549
|
-
const target =
|
|
2947
|
+
const target = path10.join(home, ".local", "share", "applications", "command-os.desktop");
|
|
2550
2948
|
return {
|
|
2551
2949
|
platform: platform2,
|
|
2552
2950
|
targetPath: target,
|
|
@@ -2570,11 +2968,11 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2570
2968
|
function installLauncher(argv = []) {
|
|
2571
2969
|
const portArg = argv.find((a) => a.startsWith("--port="));
|
|
2572
2970
|
const port = portArg ? Number(portArg.split("=")[1]) : 7300;
|
|
2573
|
-
const plan = planLauncher(process.platform,
|
|
2971
|
+
const plan = planLauncher(process.platform, os5.homedir(), port, installingEntry());
|
|
2574
2972
|
try {
|
|
2575
2973
|
for (const f of plan.files) {
|
|
2576
|
-
|
|
2577
|
-
|
|
2974
|
+
fs11.mkdirSync(path10.dirname(f.path), { recursive: true });
|
|
2975
|
+
fs11.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
|
|
2578
2976
|
}
|
|
2579
2977
|
} catch (e) {
|
|
2580
2978
|
process.stderr.write(`
|
|
@@ -2594,13 +2992,13 @@ function installLauncher(argv = []) {
|
|
|
2594
2992
|
].join("\n"));
|
|
2595
2993
|
return 0;
|
|
2596
2994
|
}
|
|
2597
|
-
var
|
|
2995
|
+
var fs11, os5, path10;
|
|
2598
2996
|
var init_launcher = __esm({
|
|
2599
2997
|
"src/launcher.ts"() {
|
|
2600
2998
|
"use strict";
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2999
|
+
fs11 = __toESM(require("fs"));
|
|
3000
|
+
os5 = __toESM(require("os"));
|
|
3001
|
+
path10 = __toESM(require("path"));
|
|
2604
3002
|
}
|
|
2605
3003
|
});
|
|
2606
3004
|
|
|
@@ -2629,10 +3027,10 @@ function stageBlockedBy(stages, stage) {
|
|
|
2629
3027
|
return null;
|
|
2630
3028
|
}
|
|
2631
3029
|
function claudeBin() {
|
|
2632
|
-
const fallback =
|
|
2633
|
-
const fromPath = (process.env.PATH || "").split(
|
|
3030
|
+
const fallback = path11.join(os6.homedir(), ".local", "bin", "claude");
|
|
3031
|
+
const fromPath = (process.env.PATH || "").split(path11.delimiter).map((d) => path11.join(d, "claude")).find((p) => {
|
|
2634
3032
|
try {
|
|
2635
|
-
|
|
3033
|
+
fs12.accessSync(p, fs12.constants.X_OK);
|
|
2636
3034
|
return true;
|
|
2637
3035
|
} catch {
|
|
2638
3036
|
return false;
|
|
@@ -2640,7 +3038,7 @@ function claudeBin() {
|
|
|
2640
3038
|
});
|
|
2641
3039
|
if (fromPath) return fromPath;
|
|
2642
3040
|
try {
|
|
2643
|
-
|
|
3041
|
+
fs12.accessSync(fallback, fs12.constants.X_OK);
|
|
2644
3042
|
return fallback;
|
|
2645
3043
|
} catch {
|
|
2646
3044
|
}
|
|
@@ -2689,7 +3087,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
2689
3087
|
const locationName = SANDBOX_ALLOWLIST[locationId2] || opts.name || locationId2;
|
|
2690
3088
|
const spec = STAGE_SPECS[stage];
|
|
2691
3089
|
if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
|
|
2692
|
-
return new Promise((
|
|
3090
|
+
return new Promise((resolve7) => {
|
|
2693
3091
|
const bin = claudeBin();
|
|
2694
3092
|
onEvent({ kind: "status", line: `Starting ${spec.name} on ${locationName} (headless Claude, ${spec.allowedTools.length} tools allowed)\u2026` });
|
|
2695
3093
|
const child = (0, import_child_process2.spawn)(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context), {
|
|
@@ -2735,31 +3133,31 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
2735
3133
|
if (code !== 0 && !lastText) {
|
|
2736
3134
|
const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}` };
|
|
2737
3135
|
onEvent({ kind: "error", line: outcome2.error });
|
|
2738
|
-
|
|
3136
|
+
resolve7(outcome2);
|
|
2739
3137
|
return;
|
|
2740
3138
|
}
|
|
2741
3139
|
const outcome = parseResultLine(lastText);
|
|
2742
3140
|
const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
|
|
2743
3141
|
const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
|
|
2744
3142
|
onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
|
|
2745
|
-
|
|
3143
|
+
resolve7(outcome);
|
|
2746
3144
|
});
|
|
2747
3145
|
child.on("error", (err) => {
|
|
2748
3146
|
clearTimeout(timeout);
|
|
2749
3147
|
const outcome = { ok: false, error: `could not start claude: ${err.message}` };
|
|
2750
3148
|
onEvent({ kind: "error", line: outcome.error });
|
|
2751
|
-
|
|
3149
|
+
resolve7(outcome);
|
|
2752
3150
|
});
|
|
2753
3151
|
});
|
|
2754
3152
|
}
|
|
2755
|
-
var import_child_process2,
|
|
3153
|
+
var import_child_process2, fs12, os6, path11, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAGE_SPECS, HUMAN_GATE_STAGES;
|
|
2756
3154
|
var init_stage_runner = __esm({
|
|
2757
3155
|
"src/stage-runner.ts"() {
|
|
2758
3156
|
"use strict";
|
|
2759
3157
|
import_child_process2 = require("child_process");
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
3158
|
+
fs12 = __toESM(require("fs"));
|
|
3159
|
+
os6 = __toESM(require("os"));
|
|
3160
|
+
path11 = __toESM(require("path"));
|
|
2763
3161
|
SANDBOX_ALLOWLIST = {
|
|
2764
3162
|
JrV2p35O3hY2wqhr2c0T: "MCP Testing",
|
|
2765
3163
|
jHP5wkYRineXDlzAOEbW: "Blueprint Demo"
|
|
@@ -3479,11 +3877,11 @@ __export(dashboard_exports, {
|
|
|
3479
3877
|
writeOverlay: () => writeOverlay
|
|
3480
3878
|
});
|
|
3481
3879
|
function overlayPath() {
|
|
3482
|
-
return
|
|
3880
|
+
return path12.join(appDataDir(), "intake-overlay.json");
|
|
3483
3881
|
}
|
|
3484
3882
|
function readOverlay() {
|
|
3485
3883
|
try {
|
|
3486
|
-
const raw = JSON.parse(
|
|
3884
|
+
const raw = JSON.parse(fs13.readFileSync(overlayPath(), "utf8"));
|
|
3487
3885
|
if (raw && typeof raw === "object") return raw;
|
|
3488
3886
|
} catch {
|
|
3489
3887
|
}
|
|
@@ -3491,7 +3889,7 @@ function readOverlay() {
|
|
|
3491
3889
|
}
|
|
3492
3890
|
function writeOverlay(layer) {
|
|
3493
3891
|
ensureAppDataDir();
|
|
3494
|
-
|
|
3892
|
+
fs13.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
|
|
3495
3893
|
}
|
|
3496
3894
|
function recoverStuckStages(state) {
|
|
3497
3895
|
let recovered = 0;
|
|
@@ -3503,11 +3901,11 @@ function recoverStuckStages(state) {
|
|
|
3503
3901
|
return { state: { ...state, clients }, recovered };
|
|
3504
3902
|
}
|
|
3505
3903
|
function cockpitStatePath() {
|
|
3506
|
-
return
|
|
3904
|
+
return path12.join(appDataDir(), "cockpit-state.json");
|
|
3507
3905
|
}
|
|
3508
3906
|
function readCockpitState() {
|
|
3509
3907
|
try {
|
|
3510
|
-
const raw = JSON.parse(
|
|
3908
|
+
const raw = JSON.parse(fs13.readFileSync(cockpitStatePath(), "utf8"));
|
|
3511
3909
|
if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
|
|
3512
3910
|
} catch {
|
|
3513
3911
|
}
|
|
@@ -3515,7 +3913,7 @@ function readCockpitState() {
|
|
|
3515
3913
|
}
|
|
3516
3914
|
function writeCockpitState(state) {
|
|
3517
3915
|
ensureAppDataDir();
|
|
3518
|
-
|
|
3916
|
+
fs13.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
3519
3917
|
}
|
|
3520
3918
|
function setStage(state, locationId2, stageIndex, status) {
|
|
3521
3919
|
if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES.length) throw new Error("bad stage index");
|
|
@@ -3607,7 +4005,7 @@ function notify(message) {
|
|
|
3607
4005
|
`);
|
|
3608
4006
|
try {
|
|
3609
4007
|
ensureAppDataDir();
|
|
3610
|
-
|
|
4008
|
+
fs13.appendFileSync(path12.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
3611
4009
|
`);
|
|
3612
4010
|
} catch {
|
|
3613
4011
|
}
|
|
@@ -4308,17 +4706,17 @@ async function runDashboard(argv) {
|
|
|
4308
4706
|
const source = args.url ? { kind: "url", url: String(args.url) } : args.instruction ? { kind: "recorder", instruction: String(args.instruction) } : { kind: "text", text: String(args.transcript ?? "") };
|
|
4309
4707
|
let recorderPatterns = [];
|
|
4310
4708
|
if (source.kind === "recorder") {
|
|
4311
|
-
recorderPatterns = await new Promise((
|
|
4709
|
+
recorderPatterns = await new Promise((resolve7) => {
|
|
4312
4710
|
const ls = (0, import_child_process4.spawn)(claudeBin(), ["mcp", "list"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4313
4711
|
let buf = "";
|
|
4314
4712
|
ls.stdout.on("data", (d) => {
|
|
4315
4713
|
buf += d.toString();
|
|
4316
4714
|
});
|
|
4317
|
-
ls.on("close", () =>
|
|
4318
|
-
ls.on("error", () =>
|
|
4715
|
+
ls.on("close", () => resolve7(recorderAllowPatterns(buf)));
|
|
4716
|
+
ls.on("error", () => resolve7([]));
|
|
4319
4717
|
setTimeout(() => {
|
|
4320
4718
|
ls.kill("SIGKILL");
|
|
4321
|
-
|
|
4719
|
+
resolve7(recorderAllowPatterns(buf));
|
|
4322
4720
|
}, 9e4).unref?.();
|
|
4323
4721
|
});
|
|
4324
4722
|
if (!recorderPatterns.length) {
|
|
@@ -4328,7 +4726,7 @@ async function runDashboard(argv) {
|
|
|
4328
4726
|
}
|
|
4329
4727
|
}
|
|
4330
4728
|
const out = await prefillFromSource(
|
|
4331
|
-
(prompt, tools) => new Promise((
|
|
4729
|
+
(prompt, tools) => new Promise((resolve7, reject) => {
|
|
4332
4730
|
const argv2 = ["-p", prompt, "--max-turns", "25", "--disallowedTools", tools.deny];
|
|
4333
4731
|
if (tools.allow) argv2.push("--allowedTools", tools.allow);
|
|
4334
4732
|
else argv2.push("--allowedTools", "");
|
|
@@ -4337,7 +4735,7 @@ async function runDashboard(argv) {
|
|
|
4337
4735
|
child.stdout.on("data", (d) => {
|
|
4338
4736
|
out2 += d.toString();
|
|
4339
4737
|
});
|
|
4340
|
-
child.on("close", () =>
|
|
4738
|
+
child.on("close", () => resolve7(out2));
|
|
4341
4739
|
child.on("error", reject);
|
|
4342
4740
|
setTimeout(() => child.kill("SIGKILL"), 5 * 60 * 1e3).unref?.();
|
|
4343
4741
|
}),
|
|
@@ -4469,13 +4867,13 @@ async function runDashboard(argv) {
|
|
|
4469
4867
|
}
|
|
4470
4868
|
}
|
|
4471
4869
|
const review = await generateReview(
|
|
4472
|
-
(prompt) => new Promise((
|
|
4870
|
+
(prompt) => new Promise((resolve7, reject) => {
|
|
4473
4871
|
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"] });
|
|
4474
4872
|
let out = "";
|
|
4475
4873
|
child.stdout.on("data", (d) => {
|
|
4476
4874
|
out += d.toString();
|
|
4477
4875
|
});
|
|
4478
|
-
child.on("close", () =>
|
|
4876
|
+
child.on("close", () => resolve7(out));
|
|
4479
4877
|
child.on("error", reject);
|
|
4480
4878
|
setTimeout(() => child.kill("SIGKILL"), 6 * 60 * 1e3).unref?.();
|
|
4481
4879
|
}),
|
|
@@ -4648,9 +5046,9 @@ async function runDashboard(argv) {
|
|
|
4648
5046
|
res.end();
|
|
4649
5047
|
});
|
|
4650
5048
|
try {
|
|
4651
|
-
await new Promise((
|
|
5049
|
+
await new Promise((resolve7, reject) => {
|
|
4652
5050
|
server2.once("error", reject);
|
|
4653
|
-
server2.listen(port, "127.0.0.1",
|
|
5051
|
+
server2.listen(port, "127.0.0.1", resolve7);
|
|
4654
5052
|
});
|
|
4655
5053
|
} catch (e) {
|
|
4656
5054
|
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)}`;
|
|
@@ -4676,18 +5074,18 @@ async function runDashboard(argv) {
|
|
|
4676
5074
|
if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
|
|
4677
5075
|
`);
|
|
4678
5076
|
});
|
|
4679
|
-
return await new Promise((
|
|
4680
|
-
const stop = () => server2.close(() =>
|
|
5077
|
+
return await new Promise((resolve7) => {
|
|
5078
|
+
const stop = () => server2.close(() => resolve7(0));
|
|
4681
5079
|
process.on("SIGINT", stop);
|
|
4682
5080
|
process.on("SIGTERM", stop);
|
|
4683
5081
|
});
|
|
4684
5082
|
}
|
|
4685
|
-
var
|
|
5083
|
+
var fs13, path12, http, import_child_process3, import_child_process4, osmod, STAGES, ACCOUNT_TYPES, activeRun, UPGRADE_MSG;
|
|
4686
5084
|
var init_dashboard = __esm({
|
|
4687
5085
|
"src/dashboard.ts"() {
|
|
4688
5086
|
"use strict";
|
|
4689
|
-
|
|
4690
|
-
|
|
5087
|
+
fs13 = __toESM(require("fs"));
|
|
5088
|
+
path12 = __toESM(require("path"));
|
|
4691
5089
|
http = __toESM(require("http"));
|
|
4692
5090
|
import_child_process3 = require("child_process");
|
|
4693
5091
|
init_credentials_store();
|
|
@@ -4722,8 +5120,8 @@ var init_dashboard = __esm({
|
|
|
4722
5120
|
|
|
4723
5121
|
// src/index.ts
|
|
4724
5122
|
var dotenv2 = __toESM(require("dotenv"));
|
|
4725
|
-
var
|
|
4726
|
-
var
|
|
5123
|
+
var path13 = __toESM(require("path"));
|
|
5124
|
+
var fs14 = __toESM(require("fs"));
|
|
4727
5125
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4728
5126
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4729
5127
|
init_ghl_client();
|
|
@@ -8758,16 +9156,16 @@ function registerEmailTools(server2, client) {
|
|
|
8758
9156
|
function registerEmailBuilderInternalTools(server2, builderClient) {
|
|
8759
9157
|
const client = builderClient;
|
|
8760
9158
|
if (!client) return;
|
|
8761
|
-
async function builderRequest(method,
|
|
9159
|
+
async function builderRequest(method, path14, body) {
|
|
8762
9160
|
const headers = await client.buildHeaders();
|
|
8763
|
-
const response = await fetch(`${EMAIL_BUILDER_BASE}${
|
|
9161
|
+
const response = await fetch(`${EMAIL_BUILDER_BASE}${path14}`, {
|
|
8764
9162
|
method,
|
|
8765
9163
|
headers,
|
|
8766
9164
|
body: body ? JSON.stringify(body) : void 0
|
|
8767
9165
|
});
|
|
8768
9166
|
if (!response.ok) {
|
|
8769
9167
|
const text2 = await response.text();
|
|
8770
|
-
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${
|
|
9168
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path14}
|
|
8771
9169
|
${text2}`);
|
|
8772
9170
|
}
|
|
8773
9171
|
const text = await response.text();
|
|
@@ -10024,23 +10422,23 @@ var import_zod36 = require("zod");
|
|
|
10024
10422
|
function registerFunnelBuilderTools(server2, builderClient) {
|
|
10025
10423
|
const client = builderClient;
|
|
10026
10424
|
if (!client) return;
|
|
10027
|
-
async function internalGet(
|
|
10028
|
-
return client.request("GET",
|
|
10425
|
+
async function internalGet(path14) {
|
|
10426
|
+
return client.request("GET", path14);
|
|
10029
10427
|
}
|
|
10030
|
-
async function internalPost(
|
|
10031
|
-
return client.request("POST",
|
|
10428
|
+
async function internalPost(path14, body) {
|
|
10429
|
+
return client.request("POST", path14, body);
|
|
10032
10430
|
}
|
|
10033
|
-
async function internalPut(
|
|
10034
|
-
return client.request("PUT",
|
|
10431
|
+
async function internalPut(path14, body) {
|
|
10432
|
+
return client.request("PUT", path14, body);
|
|
10035
10433
|
}
|
|
10036
|
-
async function internalDelete(
|
|
10037
|
-
return client.request("DELETE",
|
|
10434
|
+
async function internalDelete(path14) {
|
|
10435
|
+
return client.request("DELETE", path14);
|
|
10038
10436
|
}
|
|
10039
|
-
async function funnelRequest(method,
|
|
10437
|
+
async function funnelRequest(method, path14, body) {
|
|
10040
10438
|
const headers = await client.buildHeaders();
|
|
10041
10439
|
headers.Origin = "https://app.gohighlevel.com";
|
|
10042
10440
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
10043
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
10441
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path14}`;
|
|
10044
10442
|
const options = { method, headers };
|
|
10045
10443
|
if (body && (method === "POST" || method === "PUT")) {
|
|
10046
10444
|
options.body = JSON.stringify(body);
|
|
@@ -10048,7 +10446,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
10048
10446
|
const response = await fetch(url, options);
|
|
10049
10447
|
if (!response.ok) {
|
|
10050
10448
|
const text2 = await response.text();
|
|
10051
|
-
throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
10449
|
+
throw new Error(`Funnel API Error ${response.status}: ${method} ${path14}
|
|
10052
10450
|
${text2}`);
|
|
10053
10451
|
}
|
|
10054
10452
|
const text = await response.text();
|
|
@@ -10993,12 +11391,12 @@ var valueCardSchema = import_zod37.z.object({
|
|
|
10993
11391
|
function registerPageStudioTools(server2, builderClient) {
|
|
10994
11392
|
const client = builderClient;
|
|
10995
11393
|
if (!client) return;
|
|
10996
|
-
async function funnelRequest(method,
|
|
11394
|
+
async function funnelRequest(method, path14) {
|
|
10997
11395
|
const headers = await client.buildHeaders();
|
|
10998
11396
|
headers.Origin = "https://app.gohighlevel.com";
|
|
10999
11397
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
11000
|
-
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${
|
|
11001
|
-
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
11398
|
+
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${path14}`, { method, headers });
|
|
11399
|
+
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path14}
|
|
11002
11400
|
${await response.text()}`);
|
|
11003
11401
|
const text = await response.text();
|
|
11004
11402
|
return text ? JSON.parse(text) : {};
|
|
@@ -11317,9 +11715,9 @@ function buildUpdateFormPath(formId, locationId2) {
|
|
|
11317
11715
|
function buildUpdateFormBody(name, formData) {
|
|
11318
11716
|
return { name, formData };
|
|
11319
11717
|
}
|
|
11320
|
-
async function formApiRequest(client, method,
|
|
11718
|
+
async function formApiRequest(client, method, path14, body) {
|
|
11321
11719
|
const headers = await client.buildHeaders();
|
|
11322
|
-
const url = `https://backend.leadconnectorhq.com/forms${
|
|
11720
|
+
const url = `https://backend.leadconnectorhq.com/forms${path14}`;
|
|
11323
11721
|
const options = { method, headers };
|
|
11324
11722
|
if (body && (method === "POST" || method === "PUT")) {
|
|
11325
11723
|
options.body = JSON.stringify(body);
|
|
@@ -11327,7 +11725,7 @@ async function formApiRequest(client, method, path13, body) {
|
|
|
11327
11725
|
const response = await fetch(url, options);
|
|
11328
11726
|
if (!response.ok) {
|
|
11329
11727
|
const text2 = await response.text();
|
|
11330
|
-
throw new Error(`Form API Error ${response.status}: ${method} ${
|
|
11728
|
+
throw new Error(`Form API Error ${response.status}: ${method} ${path14}
|
|
11331
11729
|
${text2}`);
|
|
11332
11730
|
}
|
|
11333
11731
|
const text = await response.text();
|
|
@@ -11341,7 +11739,7 @@ ${text2}`);
|
|
|
11341
11739
|
function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
11342
11740
|
const client = builderClient;
|
|
11343
11741
|
if (!client) return;
|
|
11344
|
-
const formRequest = (method,
|
|
11742
|
+
const formRequest = (method, path14, body) => formApiRequest(client, method, path14, body);
|
|
11345
11743
|
server2.tool(
|
|
11346
11744
|
"get_form_full",
|
|
11347
11745
|
"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.",
|
|
@@ -11462,10 +11860,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
11462
11860
|
},
|
|
11463
11861
|
async ({ formId, limit, skip }) => {
|
|
11464
11862
|
try {
|
|
11465
|
-
let
|
|
11466
|
-
if (formId)
|
|
11467
|
-
if (skip)
|
|
11468
|
-
const result = await formRequest("GET",
|
|
11863
|
+
let path14 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
|
|
11864
|
+
if (formId) path14 += `&formId=${formId}`;
|
|
11865
|
+
if (skip) path14 += `&skip=${skip}`;
|
|
11866
|
+
const result = await formRequest("GET", path14);
|
|
11469
11867
|
return {
|
|
11470
11868
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
11471
11869
|
};
|
|
@@ -11858,9 +12256,9 @@ var import_zod40 = require("zod");
|
|
|
11858
12256
|
function registerPipelineBuilderTools(server2, builderClient) {
|
|
11859
12257
|
const client = builderClient;
|
|
11860
12258
|
if (!client) return;
|
|
11861
|
-
async function pipelineRequest(method,
|
|
12259
|
+
async function pipelineRequest(method, path14, body) {
|
|
11862
12260
|
const headers = await client.buildHeaders();
|
|
11863
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
12261
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path14}`;
|
|
11864
12262
|
const options = { method, headers };
|
|
11865
12263
|
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
11866
12264
|
options.body = JSON.stringify(body);
|
|
@@ -11868,7 +12266,7 @@ function registerPipelineBuilderTools(server2, builderClient) {
|
|
|
11868
12266
|
const response = await fetch(url, options);
|
|
11869
12267
|
if (!response.ok) {
|
|
11870
12268
|
const text2 = await response.text();
|
|
11871
|
-
throw new Error(`Pipeline API Error ${response.status}: ${method} ${
|
|
12269
|
+
throw new Error(`Pipeline API Error ${response.status}: ${method} ${path14}
|
|
11872
12270
|
${text2}`);
|
|
11873
12271
|
}
|
|
11874
12272
|
const text = await response.text();
|
|
@@ -12613,7 +13011,7 @@ ${lines.join("\n")}
|
|
|
12613
13011
|
// src/tools/bulk-operations.ts
|
|
12614
13012
|
var import_zod44 = require("zod");
|
|
12615
13013
|
function delay(ms) {
|
|
12616
|
-
return new Promise((
|
|
13014
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12617
13015
|
}
|
|
12618
13016
|
function formatResults(op, results, total) {
|
|
12619
13017
|
return `${op}: ${results.success} success, ${results.failed} failed out of ${total}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
|
|
@@ -12738,7 +13136,7 @@ function registerBulkOperationTools(server2, client) {
|
|
|
12738
13136
|
// src/tools/account-export.ts
|
|
12739
13137
|
var import_zod45 = require("zod");
|
|
12740
13138
|
function delay2(ms) {
|
|
12741
|
-
return new Promise((
|
|
13139
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12742
13140
|
}
|
|
12743
13141
|
function registerAccountExportTools(server2, client) {
|
|
12744
13142
|
const builderClient = WorkflowBuilderClient.fromEnv();
|
|
@@ -13059,9 +13457,9 @@ var OBJECT_KEYS = ["contacts", "opportunity"];
|
|
|
13059
13457
|
function registerSmartListTools(server2, builderClient) {
|
|
13060
13458
|
const client = builderClient;
|
|
13061
13459
|
if (!client) return;
|
|
13062
|
-
async function smartListRequest(method,
|
|
13460
|
+
async function smartListRequest(method, path14, body) {
|
|
13063
13461
|
const headers = await client.buildHeaders();
|
|
13064
|
-
const url = `${SMARTLIST_BASE}${
|
|
13462
|
+
const url = `${SMARTLIST_BASE}${path14}`;
|
|
13065
13463
|
const options = { method, headers };
|
|
13066
13464
|
if (body && (method === "POST" || method === "PUT")) {
|
|
13067
13465
|
options.body = JSON.stringify(body);
|
|
@@ -13069,7 +13467,7 @@ function registerSmartListTools(server2, builderClient) {
|
|
|
13069
13467
|
const response = await fetch(url, options);
|
|
13070
13468
|
if (!response.ok) {
|
|
13071
13469
|
const text2 = await response.text();
|
|
13072
|
-
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${
|
|
13470
|
+
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path14}
|
|
13073
13471
|
${text2}`);
|
|
13074
13472
|
}
|
|
13075
13473
|
const text = await response.text();
|
|
@@ -13201,12 +13599,12 @@ var REPUTATION_BASE = "https://backend.leadconnectorhq.com/reputation";
|
|
|
13201
13599
|
function registerReputationTools(server2, builderClient) {
|
|
13202
13600
|
const client = builderClient;
|
|
13203
13601
|
if (!client) return;
|
|
13204
|
-
async function reputationRequest(method,
|
|
13602
|
+
async function reputationRequest(method, path14) {
|
|
13205
13603
|
const headers = await client.buildHeaders();
|
|
13206
|
-
const response = await fetch(`${REPUTATION_BASE}${
|
|
13604
|
+
const response = await fetch(`${REPUTATION_BASE}${path14}`, { method, headers });
|
|
13207
13605
|
if (!response.ok) {
|
|
13208
13606
|
const text2 = await response.text();
|
|
13209
|
-
throw new Error(`Reputation API Error ${response.status}: ${method} ${
|
|
13607
|
+
throw new Error(`Reputation API Error ${response.status}: ${method} ${path14}
|
|
13210
13608
|
${text2}`);
|
|
13211
13609
|
}
|
|
13212
13610
|
const text = await response.text();
|
|
@@ -13321,16 +13719,16 @@ var MEMBERSHIP_BASE = "https://backend.leadconnectorhq.com/membership";
|
|
|
13321
13719
|
function registerMembershipTools(server2, builderClient) {
|
|
13322
13720
|
const client = builderClient;
|
|
13323
13721
|
if (!client) return;
|
|
13324
|
-
async function membershipRequest(
|
|
13722
|
+
async function membershipRequest(path14, method = "GET", body) {
|
|
13325
13723
|
const headers = await client.buildHeaders();
|
|
13326
|
-
const response = await fetch(`${MEMBERSHIP_BASE}${
|
|
13724
|
+
const response = await fetch(`${MEMBERSHIP_BASE}${path14}`, {
|
|
13327
13725
|
method,
|
|
13328
13726
|
headers,
|
|
13329
13727
|
body: body ? JSON.stringify(body) : void 0
|
|
13330
13728
|
});
|
|
13331
13729
|
if (!response.ok) {
|
|
13332
13730
|
const text2 = await response.text();
|
|
13333
|
-
throw new Error(`Membership API Error ${response.status}: ${method} ${
|
|
13731
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path14}
|
|
13334
13732
|
${text2}`);
|
|
13335
13733
|
}
|
|
13336
13734
|
const text = await response.text();
|
|
@@ -13499,7 +13897,7 @@ var import_zod51 = require("zod");
|
|
|
13499
13897
|
var fs7 = __toESM(require("fs"));
|
|
13500
13898
|
var path6 = __toESM(require("path"));
|
|
13501
13899
|
function delay3(ms) {
|
|
13502
|
-
return new Promise((
|
|
13900
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
13503
13901
|
}
|
|
13504
13902
|
var TemplateSchema = import_zod51.z.object({
|
|
13505
13903
|
templateName: import_zod51.z.string(),
|
|
@@ -13643,7 +14041,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
13643
14041
|
const locId = client.resolveLocationId(locationId2);
|
|
13644
14042
|
const safePath = validateTemplatePath(templateFile);
|
|
13645
14043
|
const template = TemplateSchema.parse(JSON.parse(fs7.readFileSync(safePath, "utf-8")));
|
|
13646
|
-
const
|
|
14044
|
+
const resolve7 = (text) => {
|
|
13647
14045
|
if (typeof text !== "string") return text;
|
|
13648
14046
|
let result = text;
|
|
13649
14047
|
for (const [key, value] of Object.entries(answers)) {
|
|
@@ -13656,7 +14054,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
13656
14054
|
return result;
|
|
13657
14055
|
};
|
|
13658
14056
|
const resolveObj = (obj) => {
|
|
13659
|
-
if (typeof obj === "string") return
|
|
14057
|
+
if (typeof obj === "string") return resolve7(obj);
|
|
13660
14058
|
if (Array.isArray(obj)) return obj.map(resolveObj);
|
|
13661
14059
|
if (obj && typeof obj === "object") {
|
|
13662
14060
|
const result = {};
|
|
@@ -15317,9 +15715,9 @@ function presetForBusinessType(type) {
|
|
|
15317
15715
|
return "generic";
|
|
15318
15716
|
}
|
|
15319
15717
|
}
|
|
15320
|
-
function setPath(target,
|
|
15718
|
+
function setPath(target, path14, value) {
|
|
15321
15719
|
if (value === void 0) return;
|
|
15322
|
-
const parts =
|
|
15720
|
+
const parts = path14.split(".");
|
|
15323
15721
|
let node = target;
|
|
15324
15722
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
15325
15723
|
const k = parts[i];
|
|
@@ -17405,15 +17803,15 @@ function extractFunnelId(result) {
|
|
|
17405
17803
|
return void 0;
|
|
17406
17804
|
}
|
|
17407
17805
|
function makeExecuteDeps(client, builderClient, locationId2) {
|
|
17408
|
-
const pipelineApi = async (method,
|
|
17806
|
+
const pipelineApi = async (method, path14, body) => {
|
|
17409
17807
|
const headers = await builderClient.buildHeaders();
|
|
17410
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
17808
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path14}`;
|
|
17411
17809
|
const options = { method, headers };
|
|
17412
17810
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
17413
17811
|
const response = await fetch(url, options);
|
|
17414
17812
|
if (!response.ok) {
|
|
17415
17813
|
const text2 = await response.text();
|
|
17416
|
-
throw new Error(`Pipeline API ${response.status}: ${method} ${
|
|
17814
|
+
throw new Error(`Pipeline API ${response.status}: ${method} ${path14}
|
|
17417
17815
|
${text2.slice(0, 300)}`);
|
|
17418
17816
|
}
|
|
17419
17817
|
const text = await response.text();
|
|
@@ -17424,17 +17822,17 @@ ${text2.slice(0, 300)}`);
|
|
|
17424
17822
|
return JSON.parse(text.replace(/[\x00-\x1F\x7F]/g, ""));
|
|
17425
17823
|
}
|
|
17426
17824
|
};
|
|
17427
|
-
const funnelApi = async (method,
|
|
17825
|
+
const funnelApi = async (method, path14, body) => {
|
|
17428
17826
|
const headers = await builderClient.buildHeaders();
|
|
17429
17827
|
headers.Origin = "https://app.gohighlevel.com";
|
|
17430
17828
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
17431
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
17829
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path14}`;
|
|
17432
17830
|
const options = { method, headers };
|
|
17433
17831
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
17434
17832
|
const response = await fetch(url, options);
|
|
17435
17833
|
if (!response.ok) {
|
|
17436
17834
|
const text2 = await response.text();
|
|
17437
|
-
throw new Error(`Funnel API ${response.status}: ${method} ${
|
|
17835
|
+
throw new Error(`Funnel API ${response.status}: ${method} ${path14}
|
|
17438
17836
|
${text2.slice(0, 300)}`);
|
|
17439
17837
|
}
|
|
17440
17838
|
const text = await response.text();
|
|
@@ -18351,21 +18749,29 @@ function registerMetaTools(server2, installedVersion) {
|
|
|
18351
18749
|
}
|
|
18352
18750
|
|
|
18353
18751
|
// src/cli.ts
|
|
18354
|
-
var
|
|
18355
|
-
var
|
|
18356
|
-
var
|
|
18752
|
+
var import_node_util2 = require("node:util");
|
|
18753
|
+
var fs10 = __toESM(require("fs"));
|
|
18754
|
+
var path9 = __toESM(require("path"));
|
|
18357
18755
|
var import_crypto2 = require("crypto");
|
|
18358
18756
|
init_ghl_client();
|
|
18359
18757
|
init_token_registry();
|
|
18360
18758
|
init_credentials_store();
|
|
18361
18759
|
init_setup_tool();
|
|
18362
|
-
var
|
|
18363
|
-
var
|
|
18760
|
+
var EXIT_OK2 = 0;
|
|
18761
|
+
var EXIT_USAGE2 = 2;
|
|
18364
18762
|
var EXIT_VALIDATION = 3;
|
|
18365
18763
|
var EXIT_FS = 4;
|
|
18366
18764
|
var USAGE = `Usage: ghl-mcp cli <subcommand> [options]
|
|
18367
18765
|
|
|
18368
18766
|
Subcommands:
|
|
18767
|
+
install Add GHL Command to Claude Desktop's settings file
|
|
18768
|
+
(merges safely with existing MCP servers; backs up
|
|
18769
|
+
first; repairs unreadable configs where possible)
|
|
18770
|
+
--dry-run Show what would change without writing anything
|
|
18771
|
+
--print-only Print the merged JSON instead of writing it
|
|
18772
|
+
--path <file> Explicit claude_desktop_config.json location
|
|
18773
|
+
--force Replace a different existing "ghl" entry
|
|
18774
|
+
|
|
18369
18775
|
register-location Add a sub-account's Private Integration key
|
|
18370
18776
|
--location-id <id> GHL Location ID (required)
|
|
18371
18777
|
--api-key <pit-...> The sub-account's Private Integration key (required)
|
|
@@ -18396,9 +18802,9 @@ function errLine(msg2) {
|
|
|
18396
18802
|
function preflightWritable() {
|
|
18397
18803
|
try {
|
|
18398
18804
|
const dir = ensureAppDataDir();
|
|
18399
|
-
const probe =
|
|
18400
|
-
|
|
18401
|
-
|
|
18805
|
+
const probe = path9.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
|
|
18806
|
+
fs10.writeFileSync(probe, "ok");
|
|
18807
|
+
fs10.unlinkSync(probe);
|
|
18402
18808
|
return true;
|
|
18403
18809
|
} catch (error) {
|
|
18404
18810
|
errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -18422,7 +18828,7 @@ function confirmSaved(registry2) {
|
|
|
18422
18828
|
function parse(argv, options, required) {
|
|
18423
18829
|
let parsed;
|
|
18424
18830
|
try {
|
|
18425
|
-
parsed = (0,
|
|
18831
|
+
parsed = (0, import_node_util2.parseArgs)({ args: argv, options, strict: true, allowPositionals: false });
|
|
18426
18832
|
} catch (error) {
|
|
18427
18833
|
return { usageError: error instanceof Error ? error.message : String(error) };
|
|
18428
18834
|
}
|
|
@@ -18449,7 +18855,7 @@ async function cmdRegisterLocation(argv, registry2) {
|
|
|
18449
18855
|
if ("usageError" in p) {
|
|
18450
18856
|
errLine(p.usageError);
|
|
18451
18857
|
errLine(USAGE);
|
|
18452
|
-
return
|
|
18858
|
+
return EXIT_USAGE2;
|
|
18453
18859
|
}
|
|
18454
18860
|
const locationId2 = p.values["location-id"].trim();
|
|
18455
18861
|
const apiKey2 = p.values["api-key"].trim();
|
|
@@ -18482,7 +18888,7 @@ async function cmdRegisterLocation(argv, registry2) {
|
|
|
18482
18888
|
`
|
|
18483
18889
|
);
|
|
18484
18890
|
restartReminder();
|
|
18485
|
-
return
|
|
18891
|
+
return EXIT_OK2;
|
|
18486
18892
|
}
|
|
18487
18893
|
async function cmdRegisterCompanyFirebase(argv, registry2) {
|
|
18488
18894
|
const p = parse(
|
|
@@ -18500,7 +18906,7 @@ async function cmdRegisterCompanyFirebase(argv, registry2) {
|
|
|
18500
18906
|
if ("usageError" in p) {
|
|
18501
18907
|
errLine(p.usageError);
|
|
18502
18908
|
errLine(USAGE);
|
|
18503
|
-
return
|
|
18909
|
+
return EXIT_USAGE2;
|
|
18504
18910
|
}
|
|
18505
18911
|
const typedCompanyId = p.values["company-id"].trim();
|
|
18506
18912
|
const refreshToken = p.values["refresh-token"].trim();
|
|
@@ -18510,7 +18916,7 @@ async function cmdRegisterCompanyFirebase(argv, registry2) {
|
|
|
18510
18916
|
if (!apiKey2) {
|
|
18511
18917
|
errLine("No Firebase API key available. Pass --api-key (starts with 'AIza'), or seed the home");
|
|
18512
18918
|
errLine("Firebase first (the key is identical across GHL accounts).");
|
|
18513
|
-
return
|
|
18919
|
+
return EXIT_USAGE2;
|
|
18514
18920
|
}
|
|
18515
18921
|
let canonicalCompanyId = typedCompanyId;
|
|
18516
18922
|
if (!p.values["no-validate"]) {
|
|
@@ -18549,7 +18955,7 @@ async function cmdRegisterCompanyFirebase(argv, registry2) {
|
|
|
18549
18955
|
`
|
|
18550
18956
|
);
|
|
18551
18957
|
restartReminder();
|
|
18552
|
-
return
|
|
18958
|
+
return EXIT_OK2;
|
|
18553
18959
|
}
|
|
18554
18960
|
async function cmdRegisterAgencyKey(argv, registry2) {
|
|
18555
18961
|
const p = parse(
|
|
@@ -18560,7 +18966,7 @@ async function cmdRegisterAgencyKey(argv, registry2) {
|
|
|
18560
18966
|
if ("usageError" in p) {
|
|
18561
18967
|
errLine(p.usageError);
|
|
18562
18968
|
errLine(USAGE);
|
|
18563
|
-
return
|
|
18969
|
+
return EXIT_USAGE2;
|
|
18564
18970
|
}
|
|
18565
18971
|
const apiKey2 = p.values["api-key"].trim();
|
|
18566
18972
|
if (!p.values["no-validate"]) {
|
|
@@ -18589,7 +18995,7 @@ async function cmdRegisterAgencyKey(argv, registry2) {
|
|
|
18589
18995
|
process.stdout.write(`Registered agency key: ${apiKey2.substring(0, 12)}... \u2192 ${tokenRegistryPath()}
|
|
18590
18996
|
`);
|
|
18591
18997
|
restartReminder();
|
|
18592
|
-
return
|
|
18998
|
+
return EXIT_OK2;
|
|
18593
18999
|
}
|
|
18594
19000
|
function cmdListLocations(registry2) {
|
|
18595
19001
|
const locs = registry2.listLocations().map((loc) => {
|
|
@@ -18606,15 +19012,19 @@ function cmdListLocations(registry2) {
|
|
|
18606
19012
|
companyFirebases: companies.map(({ companyId, name }) => ({ companyId, ...name ? { name } : {} }))
|
|
18607
19013
|
};
|
|
18608
19014
|
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
18609
|
-
return
|
|
19015
|
+
return EXIT_OK2;
|
|
18610
19016
|
}
|
|
18611
19017
|
async function runCli(subcommand, argv) {
|
|
19018
|
+
if (subcommand === "install") {
|
|
19019
|
+
const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_config_installer(), config_installer_exports));
|
|
19020
|
+
return runInstall2(argv);
|
|
19021
|
+
}
|
|
18612
19022
|
let registry2;
|
|
18613
19023
|
try {
|
|
18614
19024
|
registry2 = new TokenRegistry();
|
|
18615
19025
|
} catch (error) {
|
|
18616
19026
|
errLine(error instanceof Error ? error.message : String(error));
|
|
18617
|
-
return
|
|
19027
|
+
return EXIT_USAGE2;
|
|
18618
19028
|
}
|
|
18619
19029
|
const loadFailure = registry2.getLoadFailure();
|
|
18620
19030
|
if (loadFailure) {
|
|
@@ -18635,11 +19045,11 @@ async function runCli(subcommand, argv) {
|
|
|
18635
19045
|
case "--help":
|
|
18636
19046
|
case "-h":
|
|
18637
19047
|
process.stdout.write(USAGE + "\n");
|
|
18638
|
-
return subcommand === void 0 ?
|
|
19048
|
+
return subcommand === void 0 ? EXIT_USAGE2 : EXIT_OK2;
|
|
18639
19049
|
default:
|
|
18640
19050
|
errLine(`Unknown subcommand: ${subcommand}`);
|
|
18641
19051
|
errLine(USAGE);
|
|
18642
|
-
return
|
|
19052
|
+
return EXIT_USAGE2;
|
|
18643
19053
|
}
|
|
18644
19054
|
}
|
|
18645
19055
|
|
|
@@ -18648,7 +19058,7 @@ var bundledPkg = require_package();
|
|
|
18648
19058
|
var pkg = (() => {
|
|
18649
19059
|
try {
|
|
18650
19060
|
const onDisk = JSON.parse(
|
|
18651
|
-
|
|
19061
|
+
fs14.readFileSync(path13.resolve(__dirname, "..", "package.json"), "utf8")
|
|
18652
19062
|
);
|
|
18653
19063
|
if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
|
|
18654
19064
|
return { version: onDisk.version };
|
|
@@ -18661,7 +19071,7 @@ dotenv2.config();
|
|
|
18661
19071
|
setPkgVersion(pkg.version);
|
|
18662
19072
|
{
|
|
18663
19073
|
const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
18664
|
-
if (configDirOverride && !
|
|
19074
|
+
if (configDirOverride && !path13.isAbsolute(configDirOverride)) {
|
|
18665
19075
|
process.stderr.write(
|
|
18666
19076
|
`[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.
|
|
18667
19077
|
`
|
|
@@ -18674,20 +19084,20 @@ process.on("unhandledRejection", (reason) => {
|
|
|
18674
19084
|
`);
|
|
18675
19085
|
});
|
|
18676
19086
|
function hardenSecretFilePerms() {
|
|
18677
|
-
const repoDir =
|
|
19087
|
+
const repoDir = path13.resolve(__dirname, "..");
|
|
18678
19088
|
const candidates = [
|
|
18679
|
-
{ file:
|
|
19089
|
+
{ file: path13.join(repoDir, "start-mcp.sh"), mode: 448 },
|
|
18680
19090
|
// Legacy registry location (pre-migration); new location lives in app-data.
|
|
18681
|
-
{ file:
|
|
19091
|
+
{ file: path13.join(repoDir, ".ghl-tokens.json"), mode: 384 },
|
|
18682
19092
|
{ file: tokenRegistryPath(), mode: 384 }
|
|
18683
19093
|
];
|
|
18684
19094
|
for (const { file, mode } of candidates) {
|
|
18685
19095
|
let current;
|
|
18686
19096
|
try {
|
|
18687
|
-
if (!
|
|
18688
|
-
current =
|
|
19097
|
+
if (!fs14.existsSync(file)) continue;
|
|
19098
|
+
current = fs14.statSync(file).mode & 511;
|
|
18689
19099
|
if (current !== mode) {
|
|
18690
|
-
|
|
19100
|
+
fs14.chmodSync(file, mode);
|
|
18691
19101
|
}
|
|
18692
19102
|
} catch (error) {
|
|
18693
19103
|
const message = error instanceof Error ? error.message : String(error);
|