@elitedcs/ghl-mcp 3.61.0 → 3.62.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 +19 -0
- package/README.md +5 -5
- package/dist/index.js +260 -205
- package/guide/guide.html +410 -0
- package/package.json +4 -3
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(path15, params) {
|
|
118
|
+
const url = new URL(path15, 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, path15, options = {}, attempt = 0) {
|
|
129
|
+
const url = this.buildUrl(path15, 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} ${path15}`);
|
|
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} ${path15}, 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, path15, 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} ${path15}, 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, path15, 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} ${path15}
|
|
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(path15, options) {
|
|
187
|
+
return this.request("GET", path15, options);
|
|
188
188
|
}
|
|
189
|
-
async post(
|
|
190
|
-
return this.request("POST",
|
|
189
|
+
async post(path15, options) {
|
|
190
|
+
return this.request("POST", path15, options);
|
|
191
191
|
}
|
|
192
|
-
async put(
|
|
193
|
-
return this.request("PUT",
|
|
192
|
+
async put(path15, options) {
|
|
193
|
+
return this.request("PUT", path15, options);
|
|
194
194
|
}
|
|
195
|
-
async patch(
|
|
196
|
-
return this.request("PATCH",
|
|
195
|
+
async patch(path15, options) {
|
|
196
|
+
return this.request("PATCH", path15, options);
|
|
197
197
|
}
|
|
198
|
-
async delete(
|
|
199
|
-
return this.request("DELETE",
|
|
198
|
+
async delete(path15, options) {
|
|
199
|
+
return this.request("DELETE", path15, options);
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* Helper: resolves locationId from args or falls back to default
|
|
@@ -1648,7 +1648,7 @@ Note: Firebase credentials rejected (${fb.error}).`;
|
|
|
1648
1648
|
const telemetryLine = telemetryDisabled(process.env) ? "" : `
|
|
1649
1649
|
|
|
1650
1650
|
${TELEMETRY_DISCLOSURE}`;
|
|
1651
|
-
const finishedCount = isFree ? "
|
|
1651
|
+
const finishedCount = isFree ? "109" : "235";
|
|
1652
1652
|
const freeTip = isFree ? `
|
|
1653
1653
|
|
|
1654
1654
|
Free tier: read-only. Write tools stay visible but answer with upgrade info instead of acting. Full version ($97/mo founding rate) upgrades in place \u2014 same install, you only swap the license key: https://ghlcommand.com` : "";
|
|
@@ -1693,7 +1693,7 @@ Free tier: read-only. Write tools stay visible but answer with upgrade info inst
|
|
|
1693
1693
|
function registerEnableWorkflowBuilderTool(server2) {
|
|
1694
1694
|
server2.tool(
|
|
1695
1695
|
"enable_workflow_builder",
|
|
1696
|
-
"Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. 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
|
|
1696
|
+
"Add Firebase credentials to an existing GHL Command install to unlock 52 additional tools across the internal-API modules: workflow builder (create/edit/clone/delete/publish/validate workflows, build_if_else_branch, build_goal_event, get_trigger_registry), funnel + page builder, form builder, pipeline builder, workflow cloner, smart lists, reputation, email campaigns, email templates, and memberships, plus the pre-deploy validator. 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 183 to 235 after the next Claude restart.",
|
|
1697
1697
|
{
|
|
1698
1698
|
// v3.25.0: one-paste path. Tool runs `auto_capture_firebase_script` to
|
|
1699
1699
|
// get the console script; the script returns a JSON object that pastes
|
|
@@ -1774,7 +1774,7 @@ DevTools steps: https://elitedcs.com/ghl-mcp-firebase`
|
|
|
1774
1774
|
"",
|
|
1775
1775
|
"**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.",
|
|
1776
1776
|
"",
|
|
1777
|
-
'After restart, all
|
|
1777
|
+
'After restart, all 235 tools load. Try: "List my workflows in full detail" or "Validate workflow <id>".',
|
|
1778
1778
|
"",
|
|
1779
1779
|
"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."
|
|
1780
1780
|
].join("\n")
|
|
@@ -1870,7 +1870,7 @@ The login in the capture window may belong to the wrong GHL account, or the sess
|
|
|
1870
1870
|
"",
|
|
1871
1871
|
"**You MUST restart Claude before using any workflow-builder tool.** Quit Claude completely (Cmd+Q on Mac, full exit on Windows) and reopen.",
|
|
1872
1872
|
"",
|
|
1873
|
-
'After restart, all
|
|
1873
|
+
'After restart, all 235 tools load. Try: "List my workflows in full detail".',
|
|
1874
1874
|
"",
|
|
1875
1875
|
"Future token rotations re-capture silently \u2014 if workflow tools ever 401, just run capture_firebase_interactive again; no window should appear."
|
|
1876
1876
|
].join("\n")
|
|
@@ -2471,7 +2471,7 @@ function defaultIO() {
|
|
|
2471
2471
|
return {
|
|
2472
2472
|
out: (l) => process.stdout.write(l + "\n"),
|
|
2473
2473
|
err: (l) => process.stderr.write(l + "\n"),
|
|
2474
|
-
rename: (from, to) =>
|
|
2474
|
+
rename: (from, to) => fs10.renameSync(from, to),
|
|
2475
2475
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
2476
2476
|
};
|
|
2477
2477
|
}
|
|
@@ -2481,32 +2481,32 @@ function candidateConfigPaths(opts) {
|
|
|
2481
2481
|
const file = "claude_desktop_config.json";
|
|
2482
2482
|
const candidates = [];
|
|
2483
2483
|
if (platform2 === "darwin") {
|
|
2484
|
-
candidates.push(
|
|
2484
|
+
candidates.push(path9.join(home, "Library", "Application Support", "Claude", file));
|
|
2485
2485
|
} else if (platform2 === "win32") {
|
|
2486
|
-
const appData = opts?.appData ?? process.env.APPDATA ??
|
|
2487
|
-
candidates.push(
|
|
2488
|
-
const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ??
|
|
2489
|
-
const packagesDir =
|
|
2486
|
+
const appData = opts?.appData ?? process.env.APPDATA ?? path9.join(home, "AppData", "Roaming");
|
|
2487
|
+
candidates.push(path9.join(appData, "Claude", file));
|
|
2488
|
+
const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path9.join(home, "AppData", "Local");
|
|
2489
|
+
const packagesDir = path9.join(localAppData, "Packages");
|
|
2490
2490
|
try {
|
|
2491
|
-
for (const entry of
|
|
2491
|
+
for (const entry of fs10.readdirSync(packagesDir)) {
|
|
2492
2492
|
if (entry.startsWith("Claude_")) {
|
|
2493
|
-
candidates.push(
|
|
2493
|
+
candidates.push(path9.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file));
|
|
2494
2494
|
}
|
|
2495
2495
|
}
|
|
2496
2496
|
} catch {
|
|
2497
2497
|
}
|
|
2498
2498
|
} else {
|
|
2499
|
-
candidates.push(
|
|
2499
|
+
candidates.push(path9.join(home, ".config", "Claude", file));
|
|
2500
2500
|
}
|
|
2501
2501
|
return candidates;
|
|
2502
2502
|
}
|
|
2503
2503
|
function resolveConfigPath(explicitPath) {
|
|
2504
2504
|
if (explicitPath) {
|
|
2505
|
-
const p =
|
|
2506
|
-
return { configPath: p, exists:
|
|
2505
|
+
const p = path9.resolve(explicitPath);
|
|
2506
|
+
return { configPath: p, exists: fs10.existsSync(p) };
|
|
2507
2507
|
}
|
|
2508
2508
|
const candidates = candidateConfigPaths();
|
|
2509
|
-
const existing = candidates.filter((c) =>
|
|
2509
|
+
const existing = candidates.filter((c) => fs10.existsSync(c));
|
|
2510
2510
|
if (existing.length > 1) {
|
|
2511
2511
|
throw new InstallStop(
|
|
2512
2512
|
EXIT_REFUSED,
|
|
@@ -2523,22 +2523,22 @@ function resolveConfigPath(explicitPath) {
|
|
|
2523
2523
|
function resolveSymlinkPolicy(configPath, explicitPath, home = os4.homedir()) {
|
|
2524
2524
|
let st;
|
|
2525
2525
|
try {
|
|
2526
|
-
st =
|
|
2526
|
+
st = fs10.lstatSync(configPath);
|
|
2527
2527
|
} catch {
|
|
2528
2528
|
return configPath;
|
|
2529
2529
|
}
|
|
2530
2530
|
if (!st.isSymbolicLink()) return configPath;
|
|
2531
2531
|
let real;
|
|
2532
2532
|
try {
|
|
2533
|
-
real =
|
|
2533
|
+
real = fs10.realpathSync(configPath);
|
|
2534
2534
|
} catch {
|
|
2535
2535
|
throw new InstallStop(
|
|
2536
2536
|
EXIT_REFUSED,
|
|
2537
2537
|
`${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.`
|
|
2538
2538
|
);
|
|
2539
2539
|
}
|
|
2540
|
-
const rel =
|
|
2541
|
-
const outsideHome = rel.startsWith("..") ||
|
|
2540
|
+
const rel = path9.relative(path9.resolve(home), real);
|
|
2541
|
+
const outsideHome = rel.startsWith("..") || path9.isAbsolute(rel);
|
|
2542
2542
|
if (outsideHome && !explicitPath) {
|
|
2543
2543
|
throw new InstallStop(
|
|
2544
2544
|
EXIT_REFUSED,
|
|
@@ -2548,8 +2548,8 @@ function resolveSymlinkPolicy(configPath, explicitPath, home = os4.homedir()) {
|
|
|
2548
2548
|
return real;
|
|
2549
2549
|
}
|
|
2550
2550
|
function readConfigBytes(configPath) {
|
|
2551
|
-
const bytes =
|
|
2552
|
-
const stat =
|
|
2551
|
+
const bytes = fs10.readFileSync(configPath);
|
|
2552
|
+
const stat = fs10.statSync(configPath);
|
|
2553
2553
|
if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 254 && bytes[1] === 255)) {
|
|
2554
2554
|
throw new InstallStop(
|
|
2555
2555
|
EXIT_REFUSED,
|
|
@@ -2623,23 +2623,23 @@ function backupStamp(now) {
|
|
|
2623
2623
|
}
|
|
2624
2624
|
function chooseBackupPath(configPath, now = /* @__PURE__ */ new Date()) {
|
|
2625
2625
|
const base = `${configPath}.bak-${backupStamp(now)}`;
|
|
2626
|
-
if (!
|
|
2626
|
+
if (!fs10.existsSync(base)) return base;
|
|
2627
2627
|
for (let i = 2; ; i++) {
|
|
2628
2628
|
const candidate = `${base}-${i}`;
|
|
2629
|
-
if (!
|
|
2629
|
+
if (!fs10.existsSync(candidate)) return candidate;
|
|
2630
2630
|
}
|
|
2631
2631
|
}
|
|
2632
2632
|
function writeVerifiedBackup(configPath, originalBytes) {
|
|
2633
2633
|
const backupPath = chooseBackupPath(configPath);
|
|
2634
2634
|
try {
|
|
2635
|
-
|
|
2636
|
-
const readBack =
|
|
2635
|
+
fs10.writeFileSync(backupPath, originalBytes);
|
|
2636
|
+
const readBack = fs10.readFileSync(backupPath);
|
|
2637
2637
|
if (!readBack.equals(originalBytes)) {
|
|
2638
2638
|
throw new Error("backup read-back did not match");
|
|
2639
2639
|
}
|
|
2640
2640
|
} catch (e) {
|
|
2641
2641
|
try {
|
|
2642
|
-
|
|
2642
|
+
fs10.rmSync(backupPath, { force: true });
|
|
2643
2643
|
} catch {
|
|
2644
2644
|
}
|
|
2645
2645
|
throw new InstallStop(
|
|
@@ -2658,9 +2658,9 @@ function baselineOf(bytes, stat) {
|
|
|
2658
2658
|
function assertNotStale(configPath, baseline) {
|
|
2659
2659
|
let ok = false;
|
|
2660
2660
|
try {
|
|
2661
|
-
const st =
|
|
2661
|
+
const st = fs10.statSync(configPath);
|
|
2662
2662
|
if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
|
|
2663
|
-
ok = sha2562(
|
|
2663
|
+
ok = sha2562(fs10.readFileSync(configPath)) === baseline.hash;
|
|
2664
2664
|
} else {
|
|
2665
2665
|
ok = false;
|
|
2666
2666
|
}
|
|
@@ -2675,14 +2675,14 @@ function assertNotStale(configPath, baseline) {
|
|
|
2675
2675
|
}
|
|
2676
2676
|
}
|
|
2677
2677
|
async function atomicReplace(opts) {
|
|
2678
|
-
const dir =
|
|
2679
|
-
const tempPath =
|
|
2680
|
-
const fd =
|
|
2678
|
+
const dir = path9.dirname(opts.configPath);
|
|
2679
|
+
const tempPath = path9.join(dir, `.${path9.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`);
|
|
2680
|
+
const fd = fs10.openSync(tempPath, "w");
|
|
2681
2681
|
try {
|
|
2682
|
-
|
|
2683
|
-
|
|
2682
|
+
fs10.writeFileSync(fd, opts.newBytes);
|
|
2683
|
+
fs10.fsyncSync(fd);
|
|
2684
2684
|
} finally {
|
|
2685
|
-
|
|
2685
|
+
fs10.closeSync(fd);
|
|
2686
2686
|
}
|
|
2687
2687
|
try {
|
|
2688
2688
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -2707,17 +2707,17 @@ async function atomicReplace(opts) {
|
|
|
2707
2707
|
}
|
|
2708
2708
|
} catch (e) {
|
|
2709
2709
|
try {
|
|
2710
|
-
|
|
2710
|
+
fs10.rmSync(tempPath, { force: true });
|
|
2711
2711
|
} catch {
|
|
2712
2712
|
}
|
|
2713
2713
|
throw e;
|
|
2714
2714
|
}
|
|
2715
2715
|
try {
|
|
2716
|
-
const dirFd =
|
|
2716
|
+
const dirFd = fs10.openSync(dir, "r");
|
|
2717
2717
|
try {
|
|
2718
|
-
|
|
2718
|
+
fs10.fsyncSync(dirFd);
|
|
2719
2719
|
} finally {
|
|
2720
|
-
|
|
2720
|
+
fs10.closeSync(dirFd);
|
|
2721
2721
|
}
|
|
2722
2722
|
} catch {
|
|
2723
2723
|
}
|
|
@@ -2769,7 +2769,7 @@ async function runInstall(argv, ioOverride) {
|
|
|
2769
2769
|
try {
|
|
2770
2770
|
const resolved2 = resolveConfigPath(flags.path);
|
|
2771
2771
|
const configPath = resolveSymlinkPolicy(resolved2.configPath, flags.path !== void 0);
|
|
2772
|
-
const exists =
|
|
2772
|
+
const exists = fs10.existsSync(configPath);
|
|
2773
2773
|
let read = null;
|
|
2774
2774
|
let outcome;
|
|
2775
2775
|
if (exists) {
|
|
@@ -2799,7 +2799,7 @@ async function runInstall(argv, ioOverride) {
|
|
|
2799
2799
|
return EXIT_OK;
|
|
2800
2800
|
}
|
|
2801
2801
|
const backupPath = exists && read ? writeVerifiedBackup(configPath, read.bytes) : null;
|
|
2802
|
-
if (!exists)
|
|
2802
|
+
if (!exists) fs10.mkdirSync(path9.dirname(configPath), { recursive: true });
|
|
2803
2803
|
await atomicReplace({
|
|
2804
2804
|
configPath,
|
|
2805
2805
|
newBytes,
|
|
@@ -2817,13 +2817,13 @@ async function runInstall(argv, ioOverride) {
|
|
|
2817
2817
|
return EXIT_ABORTED;
|
|
2818
2818
|
}
|
|
2819
2819
|
}
|
|
2820
|
-
var
|
|
2820
|
+
var fs10, os4, path9, 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;
|
|
2821
2821
|
var init_config_installer = __esm({
|
|
2822
2822
|
"src/config-installer.ts"() {
|
|
2823
2823
|
"use strict";
|
|
2824
|
-
|
|
2824
|
+
fs10 = __toESM(require("node:fs"));
|
|
2825
2825
|
os4 = __toESM(require("node:os"));
|
|
2826
|
-
|
|
2826
|
+
path9 = __toESM(require("node:path"));
|
|
2827
2827
|
import_node_crypto3 = require("node:crypto");
|
|
2828
2828
|
import_node_util = require("node:util");
|
|
2829
2829
|
import_json5 = __toESM(require("json5"));
|
|
@@ -2854,9 +2854,9 @@ var require_package = __commonJS({
|
|
|
2854
2854
|
"package.json"(exports2, module2) {
|
|
2855
2855
|
module2.exports = {
|
|
2856
2856
|
name: "@elitedcs/ghl-mcp",
|
|
2857
|
-
version: "3.
|
|
2857
|
+
version: "3.62.0",
|
|
2858
2858
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
2859
|
-
description: "GoHighLevel MCP Server for Claude.
|
|
2859
|
+
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.",
|
|
2860
2860
|
main: "dist/index.js",
|
|
2861
2861
|
bin: {
|
|
2862
2862
|
"ghl-mcp": "dist/index.js"
|
|
@@ -2871,7 +2871,8 @@ var require_package = __commonJS({
|
|
|
2871
2871
|
"templates/external-funnel/README.md",
|
|
2872
2872
|
"README.md",
|
|
2873
2873
|
"CHANGELOG.md",
|
|
2874
|
-
"skills"
|
|
2874
|
+
"skills",
|
|
2875
|
+
"guide"
|
|
2875
2876
|
],
|
|
2876
2877
|
scripts: {
|
|
2877
2878
|
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",
|
|
@@ -2945,7 +2946,7 @@ function launchCommand(port = 7300) {
|
|
|
2945
2946
|
return `npx -y @elitedcs/ghl-mcp@latest dashboard --port=${port}`;
|
|
2946
2947
|
}
|
|
2947
2948
|
function installingEntry() {
|
|
2948
|
-
return
|
|
2949
|
+
return path11.join(__dirname, "index.js");
|
|
2949
2950
|
}
|
|
2950
2951
|
function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
2951
2952
|
const cmd = launchCommand(port);
|
|
@@ -2953,12 +2954,12 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2953
2954
|
const systemApps = "/Applications";
|
|
2954
2955
|
let base = home;
|
|
2955
2956
|
try {
|
|
2956
|
-
|
|
2957
|
+
fs12.accessSync(systemApps, fs12.constants.W_OK);
|
|
2957
2958
|
base = "";
|
|
2958
2959
|
} catch {
|
|
2959
2960
|
}
|
|
2960
|
-
const appDir = base ?
|
|
2961
|
-
const macOSDir =
|
|
2961
|
+
const appDir = base ? path11.join(home, "Applications", "Command OS.app") : path11.join(systemApps, "Command OS.app");
|
|
2962
|
+
const macOSDir = path11.join(appDir, "Contents", "MacOS");
|
|
2962
2963
|
const script = [
|
|
2963
2964
|
"#!/bin/bash",
|
|
2964
2965
|
"# GHL Command \u2014 Command OS launcher (regenerate: ghl-mcp install-launcher)",
|
|
@@ -2987,13 +2988,13 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
2987
2988
|
targetPath: appDir,
|
|
2988
2989
|
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",
|
|
2989
2990
|
files: [
|
|
2990
|
-
{ path:
|
|
2991
|
-
{ path:
|
|
2991
|
+
{ path: path11.join(macOSDir, "command-os"), contents: script, executable: true },
|
|
2992
|
+
{ path: path11.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
|
|
2992
2993
|
]
|
|
2993
2994
|
};
|
|
2994
2995
|
}
|
|
2995
2996
|
if (platform2 === "win32") {
|
|
2996
|
-
const target2 =
|
|
2997
|
+
const target2 = path11.join(home, "Desktop", "Command OS.cmd");
|
|
2997
2998
|
return {
|
|
2998
2999
|
platform: platform2,
|
|
2999
3000
|
targetPath: target2,
|
|
@@ -3006,7 +3007,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
3006
3007
|
].join("\r\n"), executable: false }]
|
|
3007
3008
|
};
|
|
3008
3009
|
}
|
|
3009
|
-
const target =
|
|
3010
|
+
const target = path11.join(home, ".local", "share", "applications", "command-os.desktop");
|
|
3010
3011
|
return {
|
|
3011
3012
|
platform: platform2,
|
|
3012
3013
|
targetPath: target,
|
|
@@ -3033,8 +3034,8 @@ function installLauncher(argv = []) {
|
|
|
3033
3034
|
const plan = planLauncher(process.platform, os5.homedir(), port, installingEntry());
|
|
3034
3035
|
try {
|
|
3035
3036
|
for (const f of plan.files) {
|
|
3036
|
-
|
|
3037
|
-
|
|
3037
|
+
fs12.mkdirSync(path11.dirname(f.path), { recursive: true });
|
|
3038
|
+
fs12.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
|
|
3038
3039
|
}
|
|
3039
3040
|
} catch (e) {
|
|
3040
3041
|
process.stderr.write(`
|
|
@@ -3054,13 +3055,13 @@ function installLauncher(argv = []) {
|
|
|
3054
3055
|
].join("\n"));
|
|
3055
3056
|
return 0;
|
|
3056
3057
|
}
|
|
3057
|
-
var
|
|
3058
|
+
var fs12, os5, path11;
|
|
3058
3059
|
var init_launcher = __esm({
|
|
3059
3060
|
"src/launcher.ts"() {
|
|
3060
3061
|
"use strict";
|
|
3061
|
-
|
|
3062
|
+
fs12 = __toESM(require("fs"));
|
|
3062
3063
|
os5 = __toESM(require("os"));
|
|
3063
|
-
|
|
3064
|
+
path11 = __toESM(require("path"));
|
|
3064
3065
|
}
|
|
3065
3066
|
});
|
|
3066
3067
|
|
|
@@ -3089,10 +3090,10 @@ function stageBlockedBy(stages, stage) {
|
|
|
3089
3090
|
return null;
|
|
3090
3091
|
}
|
|
3091
3092
|
function claudeBin() {
|
|
3092
|
-
const fallback =
|
|
3093
|
-
const fromPath = (process.env.PATH || "").split(
|
|
3093
|
+
const fallback = path12.join(os6.homedir(), ".local", "bin", "claude");
|
|
3094
|
+
const fromPath = (process.env.PATH || "").split(path12.delimiter).map((d) => path12.join(d, "claude")).find((p) => {
|
|
3094
3095
|
try {
|
|
3095
|
-
|
|
3096
|
+
fs13.accessSync(p, fs13.constants.X_OK);
|
|
3096
3097
|
return true;
|
|
3097
3098
|
} catch {
|
|
3098
3099
|
return false;
|
|
@@ -3100,7 +3101,7 @@ function claudeBin() {
|
|
|
3100
3101
|
});
|
|
3101
3102
|
if (fromPath) return fromPath;
|
|
3102
3103
|
try {
|
|
3103
|
-
|
|
3104
|
+
fs13.accessSync(fallback, fs13.constants.X_OK);
|
|
3104
3105
|
return fallback;
|
|
3105
3106
|
} catch {
|
|
3106
3107
|
}
|
|
@@ -3149,10 +3150,10 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
3149
3150
|
const locationName = SANDBOX_ALLOWLIST[locationId2] || opts.name || locationId2;
|
|
3150
3151
|
const spec = STAGE_SPECS[stage];
|
|
3151
3152
|
if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
|
|
3152
|
-
return new Promise((
|
|
3153
|
+
return new Promise((resolve8) => {
|
|
3153
3154
|
const bin = claudeBin();
|
|
3154
3155
|
onEvent({ kind: "status", line: `Starting ${spec.name} on ${locationName} (headless Claude, ${spec.allowedTools.length} tools allowed)\u2026` });
|
|
3155
|
-
const child = (0,
|
|
3156
|
+
const child = (0, import_child_process3.spawn)(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context), {
|
|
3156
3157
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3157
3158
|
env: { ...process.env }
|
|
3158
3159
|
});
|
|
@@ -3195,31 +3196,31 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
3195
3196
|
if (code !== 0 && !lastText) {
|
|
3196
3197
|
const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}` };
|
|
3197
3198
|
onEvent({ kind: "error", line: outcome2.error });
|
|
3198
|
-
|
|
3199
|
+
resolve8(outcome2);
|
|
3199
3200
|
return;
|
|
3200
3201
|
}
|
|
3201
3202
|
const outcome = parseResultLine(lastText);
|
|
3202
3203
|
const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
|
|
3203
3204
|
const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
|
|
3204
3205
|
onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
|
|
3205
|
-
|
|
3206
|
+
resolve8(outcome);
|
|
3206
3207
|
});
|
|
3207
3208
|
child.on("error", (err) => {
|
|
3208
3209
|
clearTimeout(timeout);
|
|
3209
3210
|
const outcome = { ok: false, error: `could not start claude: ${err.message}` };
|
|
3210
3211
|
onEvent({ kind: "error", line: outcome.error });
|
|
3211
|
-
|
|
3212
|
+
resolve8(outcome);
|
|
3212
3213
|
});
|
|
3213
3214
|
});
|
|
3214
3215
|
}
|
|
3215
|
-
var
|
|
3216
|
+
var import_child_process3, fs13, os6, path12, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAGE_SPECS, HUMAN_GATE_STAGES;
|
|
3216
3217
|
var init_stage_runner = __esm({
|
|
3217
3218
|
"src/stage-runner.ts"() {
|
|
3218
3219
|
"use strict";
|
|
3219
|
-
|
|
3220
|
-
|
|
3220
|
+
import_child_process3 = require("child_process");
|
|
3221
|
+
fs13 = __toESM(require("fs"));
|
|
3221
3222
|
os6 = __toESM(require("os"));
|
|
3222
|
-
|
|
3223
|
+
path12 = __toESM(require("path"));
|
|
3223
3224
|
SANDBOX_ALLOWLIST = {
|
|
3224
3225
|
JrV2p35O3hY2wqhr2c0T: "MCP Testing",
|
|
3225
3226
|
jHP5wkYRineXDlzAOEbW: "Blueprint Demo"
|
|
@@ -3939,11 +3940,11 @@ __export(dashboard_exports, {
|
|
|
3939
3940
|
writeOverlay: () => writeOverlay
|
|
3940
3941
|
});
|
|
3941
3942
|
function overlayPath() {
|
|
3942
|
-
return
|
|
3943
|
+
return path13.join(appDataDir(), "intake-overlay.json");
|
|
3943
3944
|
}
|
|
3944
3945
|
function readOverlay() {
|
|
3945
3946
|
try {
|
|
3946
|
-
const raw = JSON.parse(
|
|
3947
|
+
const raw = JSON.parse(fs14.readFileSync(overlayPath(), "utf8"));
|
|
3947
3948
|
if (raw && typeof raw === "object") return raw;
|
|
3948
3949
|
} catch {
|
|
3949
3950
|
}
|
|
@@ -3951,7 +3952,7 @@ function readOverlay() {
|
|
|
3951
3952
|
}
|
|
3952
3953
|
function writeOverlay(layer) {
|
|
3953
3954
|
ensureAppDataDir();
|
|
3954
|
-
|
|
3955
|
+
fs14.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
|
|
3955
3956
|
}
|
|
3956
3957
|
function recoverStuckStages(state) {
|
|
3957
3958
|
let recovered = 0;
|
|
@@ -3963,11 +3964,11 @@ function recoverStuckStages(state) {
|
|
|
3963
3964
|
return { state: { ...state, clients }, recovered };
|
|
3964
3965
|
}
|
|
3965
3966
|
function cockpitStatePath() {
|
|
3966
|
-
return
|
|
3967
|
+
return path13.join(appDataDir(), "cockpit-state.json");
|
|
3967
3968
|
}
|
|
3968
3969
|
function readCockpitState() {
|
|
3969
3970
|
try {
|
|
3970
|
-
const raw = JSON.parse(
|
|
3971
|
+
const raw = JSON.parse(fs14.readFileSync(cockpitStatePath(), "utf8"));
|
|
3971
3972
|
if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
|
|
3972
3973
|
} catch {
|
|
3973
3974
|
}
|
|
@@ -3975,7 +3976,7 @@ function readCockpitState() {
|
|
|
3975
3976
|
}
|
|
3976
3977
|
function writeCockpitState(state) {
|
|
3977
3978
|
ensureAppDataDir();
|
|
3978
|
-
|
|
3979
|
+
fs14.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
|
|
3979
3980
|
}
|
|
3980
3981
|
function setStage(state, locationId2, stageIndex, status) {
|
|
3981
3982
|
if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES.length) throw new Error("bad stage index");
|
|
@@ -4067,13 +4068,13 @@ function notify(message) {
|
|
|
4067
4068
|
`);
|
|
4068
4069
|
try {
|
|
4069
4070
|
ensureAppDataDir();
|
|
4070
|
-
|
|
4071
|
+
fs14.appendFileSync(path13.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
4071
4072
|
`);
|
|
4072
4073
|
} catch {
|
|
4073
4074
|
}
|
|
4074
4075
|
if (!process.stdout.isTTY && process.platform === "darwin") {
|
|
4075
4076
|
try {
|
|
4076
|
-
(0,
|
|
4077
|
+
(0, import_child_process4.spawn)(
|
|
4077
4078
|
"/usr/bin/osascript",
|
|
4078
4079
|
["-e", `display dialog ${JSON.stringify(message)} with title "Command OS" buttons {"OK"} default button "OK"`],
|
|
4079
4080
|
{ stdio: "ignore", detached: true }
|
|
@@ -4086,7 +4087,7 @@ function openBrowser(port) {
|
|
|
4086
4087
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
4087
4088
|
const openArgs = process.platform === "win32" ? ["/c", "start", "", `http://localhost:${port}`] : [`http://localhost:${port}`];
|
|
4088
4089
|
try {
|
|
4089
|
-
(0,
|
|
4090
|
+
(0, import_child_process4.spawn)(opener, openArgs, { stdio: "ignore", detached: true }).unref();
|
|
4090
4091
|
} catch {
|
|
4091
4092
|
}
|
|
4092
4093
|
}
|
|
@@ -4768,17 +4769,17 @@ async function runDashboard(argv) {
|
|
|
4768
4769
|
const source = args.url ? { kind: "url", url: String(args.url) } : args.instruction ? { kind: "recorder", instruction: String(args.instruction) } : { kind: "text", text: String(args.transcript ?? "") };
|
|
4769
4770
|
let recorderPatterns = [];
|
|
4770
4771
|
if (source.kind === "recorder") {
|
|
4771
|
-
recorderPatterns = await new Promise((
|
|
4772
|
-
const ls = (0,
|
|
4772
|
+
recorderPatterns = await new Promise((resolve8) => {
|
|
4773
|
+
const ls = (0, import_child_process5.spawn)(claudeBin(), ["mcp", "list"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4773
4774
|
let buf = "";
|
|
4774
4775
|
ls.stdout.on("data", (d) => {
|
|
4775
4776
|
buf += d.toString();
|
|
4776
4777
|
});
|
|
4777
|
-
ls.on("close", () =>
|
|
4778
|
-
ls.on("error", () =>
|
|
4778
|
+
ls.on("close", () => resolve8(recorderAllowPatterns(buf)));
|
|
4779
|
+
ls.on("error", () => resolve8([]));
|
|
4779
4780
|
setTimeout(() => {
|
|
4780
4781
|
ls.kill("SIGKILL");
|
|
4781
|
-
|
|
4782
|
+
resolve8(recorderAllowPatterns(buf));
|
|
4782
4783
|
}, 9e4).unref?.();
|
|
4783
4784
|
});
|
|
4784
4785
|
if (!recorderPatterns.length) {
|
|
@@ -4788,16 +4789,16 @@ async function runDashboard(argv) {
|
|
|
4788
4789
|
}
|
|
4789
4790
|
}
|
|
4790
4791
|
const out = await prefillFromSource(
|
|
4791
|
-
(prompt, tools) => new Promise((
|
|
4792
|
+
(prompt, tools) => new Promise((resolve8, reject) => {
|
|
4792
4793
|
const argv2 = ["-p", prompt, "--max-turns", "25", "--disallowedTools", tools.deny];
|
|
4793
4794
|
if (tools.allow) argv2.push("--allowedTools", tools.allow);
|
|
4794
4795
|
else argv2.push("--allowedTools", "");
|
|
4795
|
-
const child = (0,
|
|
4796
|
+
const child = (0, import_child_process5.spawn)(claudeBin(), argv2, { stdio: ["ignore", "pipe", "pipe"] });
|
|
4796
4797
|
let out2 = "";
|
|
4797
4798
|
child.stdout.on("data", (d) => {
|
|
4798
4799
|
out2 += d.toString();
|
|
4799
4800
|
});
|
|
4800
|
-
child.on("close", () =>
|
|
4801
|
+
child.on("close", () => resolve8(out2));
|
|
4801
4802
|
child.on("error", reject);
|
|
4802
4803
|
setTimeout(() => child.kill("SIGKILL"), 5 * 60 * 1e3).unref?.();
|
|
4803
4804
|
}),
|
|
@@ -4929,13 +4930,13 @@ async function runDashboard(argv) {
|
|
|
4929
4930
|
}
|
|
4930
4931
|
}
|
|
4931
4932
|
const review = await generateReview(
|
|
4932
|
-
(prompt) => new Promise((
|
|
4933
|
-
const child = (0,
|
|
4933
|
+
(prompt) => new Promise((resolve8, reject) => {
|
|
4934
|
+
const child = (0, import_child_process5.spawn)(claudeBin(), ["-p", prompt, "--allowedTools", "", "--disallowedTools", "Bash,Write,Edit,WebFetch,WebSearch,Task,Read,Glob,Grep", "--max-turns", "6"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
4934
4935
|
let out = "";
|
|
4935
4936
|
child.stdout.on("data", (d) => {
|
|
4936
4937
|
out += d.toString();
|
|
4937
4938
|
});
|
|
4938
|
-
child.on("close", () =>
|
|
4939
|
+
child.on("close", () => resolve8(out));
|
|
4939
4940
|
child.on("error", reject);
|
|
4940
4941
|
setTimeout(() => child.kill("SIGKILL"), 6 * 60 * 1e3).unref?.();
|
|
4941
4942
|
}),
|
|
@@ -5108,9 +5109,9 @@ async function runDashboard(argv) {
|
|
|
5108
5109
|
res.end();
|
|
5109
5110
|
});
|
|
5110
5111
|
try {
|
|
5111
|
-
await new Promise((
|
|
5112
|
+
await new Promise((resolve8, reject) => {
|
|
5112
5113
|
server2.once("error", reject);
|
|
5113
|
-
server2.listen(port, "127.0.0.1",
|
|
5114
|
+
server2.listen(port, "127.0.0.1", resolve8);
|
|
5114
5115
|
});
|
|
5115
5116
|
} catch (e) {
|
|
5116
5117
|
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)}`;
|
|
@@ -5136,20 +5137,20 @@ async function runDashboard(argv) {
|
|
|
5136
5137
|
if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
|
|
5137
5138
|
`);
|
|
5138
5139
|
});
|
|
5139
|
-
return await new Promise((
|
|
5140
|
-
const stop = () => server2.close(() =>
|
|
5140
|
+
return await new Promise((resolve8) => {
|
|
5141
|
+
const stop = () => server2.close(() => resolve8(0));
|
|
5141
5142
|
process.on("SIGINT", stop);
|
|
5142
5143
|
process.on("SIGTERM", stop);
|
|
5143
5144
|
});
|
|
5144
5145
|
}
|
|
5145
|
-
var
|
|
5146
|
+
var fs14, path13, http, import_child_process4, import_child_process5, osmod, STAGES, ACCOUNT_TYPES, activeRun, UPGRADE_MSG;
|
|
5146
5147
|
var init_dashboard = __esm({
|
|
5147
5148
|
"src/dashboard.ts"() {
|
|
5148
5149
|
"use strict";
|
|
5149
|
-
|
|
5150
|
-
|
|
5150
|
+
fs14 = __toESM(require("fs"));
|
|
5151
|
+
path13 = __toESM(require("path"));
|
|
5151
5152
|
http = __toESM(require("http"));
|
|
5152
|
-
|
|
5153
|
+
import_child_process4 = require("child_process");
|
|
5153
5154
|
init_credentials_store();
|
|
5154
5155
|
init_attestation();
|
|
5155
5156
|
init_setup_tool();
|
|
@@ -5161,7 +5162,7 @@ var init_dashboard = __esm({
|
|
|
5161
5162
|
init_client_intake();
|
|
5162
5163
|
init_blueprint_review();
|
|
5163
5164
|
init_stage_runner();
|
|
5164
|
-
|
|
5165
|
+
import_child_process5 = require("child_process");
|
|
5165
5166
|
init_ghl_client();
|
|
5166
5167
|
init_shared_brain();
|
|
5167
5168
|
osmod = __toESM(require("os"));
|
|
@@ -5182,8 +5183,8 @@ var init_dashboard = __esm({
|
|
|
5182
5183
|
|
|
5183
5184
|
// src/index.ts
|
|
5184
5185
|
var dotenv2 = __toESM(require("dotenv"));
|
|
5185
|
-
var
|
|
5186
|
-
var
|
|
5186
|
+
var path14 = __toESM(require("path"));
|
|
5187
|
+
var fs15 = __toESM(require("fs"));
|
|
5187
5188
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5188
5189
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5189
5190
|
init_ghl_client();
|
|
@@ -9340,16 +9341,16 @@ function registerEmailTools(server2, client) {
|
|
|
9340
9341
|
function registerEmailBuilderInternalTools(server2, builderClient) {
|
|
9341
9342
|
const client = builderClient;
|
|
9342
9343
|
if (!client) return;
|
|
9343
|
-
async function builderRequest(method,
|
|
9344
|
+
async function builderRequest(method, path15, body) {
|
|
9344
9345
|
const headers = await client.buildHeaders();
|
|
9345
|
-
const response = await fetch(`${EMAIL_BUILDER_BASE}${
|
|
9346
|
+
const response = await fetch(`${EMAIL_BUILDER_BASE}${path15}`, {
|
|
9346
9347
|
method,
|
|
9347
9348
|
headers,
|
|
9348
9349
|
body: body ? JSON.stringify(body) : void 0
|
|
9349
9350
|
});
|
|
9350
9351
|
if (!response.ok) {
|
|
9351
9352
|
const text2 = await response.text();
|
|
9352
|
-
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${
|
|
9353
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path15}
|
|
9353
9354
|
${text2}`);
|
|
9354
9355
|
}
|
|
9355
9356
|
const text = await response.text();
|
|
@@ -10606,23 +10607,23 @@ var import_zod37 = require("zod");
|
|
|
10606
10607
|
function registerFunnelBuilderTools(server2, builderClient) {
|
|
10607
10608
|
const client = builderClient;
|
|
10608
10609
|
if (!client) return;
|
|
10609
|
-
async function internalGet(
|
|
10610
|
-
return client.request("GET",
|
|
10610
|
+
async function internalGet(path15) {
|
|
10611
|
+
return client.request("GET", path15);
|
|
10611
10612
|
}
|
|
10612
|
-
async function internalPost(
|
|
10613
|
-
return client.request("POST",
|
|
10613
|
+
async function internalPost(path15, body) {
|
|
10614
|
+
return client.request("POST", path15, body);
|
|
10614
10615
|
}
|
|
10615
|
-
async function internalPut(
|
|
10616
|
-
return client.request("PUT",
|
|
10616
|
+
async function internalPut(path15, body) {
|
|
10617
|
+
return client.request("PUT", path15, body);
|
|
10617
10618
|
}
|
|
10618
|
-
async function internalDelete(
|
|
10619
|
-
return client.request("DELETE",
|
|
10619
|
+
async function internalDelete(path15) {
|
|
10620
|
+
return client.request("DELETE", path15);
|
|
10620
10621
|
}
|
|
10621
|
-
async function funnelRequest(method,
|
|
10622
|
+
async function funnelRequest(method, path15, body) {
|
|
10622
10623
|
const headers = await client.buildHeaders();
|
|
10623
10624
|
headers.Origin = "https://app.gohighlevel.com";
|
|
10624
10625
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
10625
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
10626
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path15}`;
|
|
10626
10627
|
const options = { method, headers };
|
|
10627
10628
|
if (body && (method === "POST" || method === "PUT")) {
|
|
10628
10629
|
options.body = JSON.stringify(body);
|
|
@@ -10630,7 +10631,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
10630
10631
|
const response = await fetch(url, options);
|
|
10631
10632
|
if (!response.ok) {
|
|
10632
10633
|
const text2 = await response.text();
|
|
10633
|
-
throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
10634
|
+
throw new Error(`Funnel API Error ${response.status}: ${method} ${path15}
|
|
10634
10635
|
${text2}`);
|
|
10635
10636
|
}
|
|
10636
10637
|
const text = await response.text();
|
|
@@ -11575,12 +11576,12 @@ var valueCardSchema = import_zod38.z.object({
|
|
|
11575
11576
|
function registerPageStudioTools(server2, builderClient) {
|
|
11576
11577
|
const client = builderClient;
|
|
11577
11578
|
if (!client) return;
|
|
11578
|
-
async function funnelRequest(method,
|
|
11579
|
+
async function funnelRequest(method, path15) {
|
|
11579
11580
|
const headers = await client.buildHeaders();
|
|
11580
11581
|
headers.Origin = "https://app.gohighlevel.com";
|
|
11581
11582
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
11582
|
-
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${
|
|
11583
|
-
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
11583
|
+
const response = await fetch(`https://backend.leadconnectorhq.com/funnels${path15}`, { method, headers });
|
|
11584
|
+
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path15}
|
|
11584
11585
|
${await response.text()}`);
|
|
11585
11586
|
const text = await response.text();
|
|
11586
11587
|
return text ? JSON.parse(text) : {};
|
|
@@ -11899,9 +11900,9 @@ function buildUpdateFormPath(formId, locationId2) {
|
|
|
11899
11900
|
function buildUpdateFormBody(name, formData) {
|
|
11900
11901
|
return { name, formData };
|
|
11901
11902
|
}
|
|
11902
|
-
async function formApiRequest(client, method,
|
|
11903
|
+
async function formApiRequest(client, method, path15, body) {
|
|
11903
11904
|
const headers = await client.buildHeaders();
|
|
11904
|
-
const url = `https://backend.leadconnectorhq.com/forms${
|
|
11905
|
+
const url = `https://backend.leadconnectorhq.com/forms${path15}`;
|
|
11905
11906
|
const options = { method, headers };
|
|
11906
11907
|
if (body && (method === "POST" || method === "PUT")) {
|
|
11907
11908
|
options.body = JSON.stringify(body);
|
|
@@ -11909,7 +11910,7 @@ async function formApiRequest(client, method, path14, body) {
|
|
|
11909
11910
|
const response = await fetch(url, options);
|
|
11910
11911
|
if (!response.ok) {
|
|
11911
11912
|
const text2 = await response.text();
|
|
11912
|
-
throw new Error(`Form API Error ${response.status}: ${method} ${
|
|
11913
|
+
throw new Error(`Form API Error ${response.status}: ${method} ${path15}
|
|
11913
11914
|
${text2}`);
|
|
11914
11915
|
}
|
|
11915
11916
|
const text = await response.text();
|
|
@@ -11923,7 +11924,7 @@ ${text2}`);
|
|
|
11923
11924
|
function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
11924
11925
|
const client = builderClient;
|
|
11925
11926
|
if (!client) return;
|
|
11926
|
-
const formRequest = (method,
|
|
11927
|
+
const formRequest = (method, path15, body) => formApiRequest(client, method, path15, body);
|
|
11927
11928
|
server2.tool(
|
|
11928
11929
|
"get_form_full",
|
|
11929
11930
|
"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.",
|
|
@@ -12044,10 +12045,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
12044
12045
|
},
|
|
12045
12046
|
async ({ formId, limit, skip }) => {
|
|
12046
12047
|
try {
|
|
12047
|
-
let
|
|
12048
|
-
if (formId)
|
|
12049
|
-
if (skip)
|
|
12050
|
-
const result = await formRequest("GET",
|
|
12048
|
+
let path15 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
|
|
12049
|
+
if (formId) path15 += `&formId=${formId}`;
|
|
12050
|
+
if (skip) path15 += `&skip=${skip}`;
|
|
12051
|
+
const result = await formRequest("GET", path15);
|
|
12051
12052
|
return {
|
|
12052
12053
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
12053
12054
|
};
|
|
@@ -12440,9 +12441,9 @@ var import_zod41 = require("zod");
|
|
|
12440
12441
|
function registerPipelineBuilderTools(server2, builderClient) {
|
|
12441
12442
|
const client = builderClient;
|
|
12442
12443
|
if (!client) return;
|
|
12443
|
-
async function pipelineRequest(method,
|
|
12444
|
+
async function pipelineRequest(method, path15, body) {
|
|
12444
12445
|
const headers = await client.buildHeaders();
|
|
12445
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
12446
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path15}`;
|
|
12446
12447
|
const options = { method, headers };
|
|
12447
12448
|
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
12448
12449
|
options.body = JSON.stringify(body);
|
|
@@ -12450,7 +12451,7 @@ function registerPipelineBuilderTools(server2, builderClient) {
|
|
|
12450
12451
|
const response = await fetch(url, options);
|
|
12451
12452
|
if (!response.ok) {
|
|
12452
12453
|
const text2 = await response.text();
|
|
12453
|
-
throw new Error(`Pipeline API Error ${response.status}: ${method} ${
|
|
12454
|
+
throw new Error(`Pipeline API Error ${response.status}: ${method} ${path15}
|
|
12454
12455
|
${text2}`);
|
|
12455
12456
|
}
|
|
12456
12457
|
const text = await response.text();
|
|
@@ -13195,7 +13196,7 @@ ${lines.join("\n")}
|
|
|
13195
13196
|
// src/tools/bulk-operations.ts
|
|
13196
13197
|
var import_zod45 = require("zod");
|
|
13197
13198
|
function delay(ms) {
|
|
13198
|
-
return new Promise((
|
|
13199
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
13199
13200
|
}
|
|
13200
13201
|
function formatResults(op, results, total) {
|
|
13201
13202
|
return `${op}: ${results.success} success, ${results.failed} failed out of ${total}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
|
|
@@ -13320,7 +13321,7 @@ function registerBulkOperationTools(server2, client) {
|
|
|
13320
13321
|
// src/tools/account-export.ts
|
|
13321
13322
|
var import_zod46 = require("zod");
|
|
13322
13323
|
function delay2(ms) {
|
|
13323
|
-
return new Promise((
|
|
13324
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
13324
13325
|
}
|
|
13325
13326
|
function registerAccountExportTools(server2, client) {
|
|
13326
13327
|
const builderClient = WorkflowBuilderClient.fromEnv();
|
|
@@ -13641,9 +13642,9 @@ var OBJECT_KEYS = ["contacts", "opportunity"];
|
|
|
13641
13642
|
function registerSmartListTools(server2, builderClient) {
|
|
13642
13643
|
const client = builderClient;
|
|
13643
13644
|
if (!client) return;
|
|
13644
|
-
async function smartListRequest(method,
|
|
13645
|
+
async function smartListRequest(method, path15, body) {
|
|
13645
13646
|
const headers = await client.buildHeaders();
|
|
13646
|
-
const url = `${SMARTLIST_BASE}${
|
|
13647
|
+
const url = `${SMARTLIST_BASE}${path15}`;
|
|
13647
13648
|
const options = { method, headers };
|
|
13648
13649
|
if (body && (method === "POST" || method === "PUT")) {
|
|
13649
13650
|
options.body = JSON.stringify(body);
|
|
@@ -13651,7 +13652,7 @@ function registerSmartListTools(server2, builderClient) {
|
|
|
13651
13652
|
const response = await fetch(url, options);
|
|
13652
13653
|
if (!response.ok) {
|
|
13653
13654
|
const text2 = await response.text();
|
|
13654
|
-
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${
|
|
13655
|
+
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path15}
|
|
13655
13656
|
${text2}`);
|
|
13656
13657
|
}
|
|
13657
13658
|
const text = await response.text();
|
|
@@ -13783,12 +13784,12 @@ var REPUTATION_BASE = "https://backend.leadconnectorhq.com/reputation";
|
|
|
13783
13784
|
function registerReputationTools(server2, builderClient) {
|
|
13784
13785
|
const client = builderClient;
|
|
13785
13786
|
if (!client) return;
|
|
13786
|
-
async function reputationRequest(method,
|
|
13787
|
+
async function reputationRequest(method, path15) {
|
|
13787
13788
|
const headers = await client.buildHeaders();
|
|
13788
|
-
const response = await fetch(`${REPUTATION_BASE}${
|
|
13789
|
+
const response = await fetch(`${REPUTATION_BASE}${path15}`, { method, headers });
|
|
13789
13790
|
if (!response.ok) {
|
|
13790
13791
|
const text2 = await response.text();
|
|
13791
|
-
throw new Error(`Reputation API Error ${response.status}: ${method} ${
|
|
13792
|
+
throw new Error(`Reputation API Error ${response.status}: ${method} ${path15}
|
|
13792
13793
|
${text2}`);
|
|
13793
13794
|
}
|
|
13794
13795
|
const text = await response.text();
|
|
@@ -13903,16 +13904,16 @@ var MEMBERSHIP_BASE = "https://backend.leadconnectorhq.com/membership";
|
|
|
13903
13904
|
function registerMembershipTools(server2, builderClient) {
|
|
13904
13905
|
const client = builderClient;
|
|
13905
13906
|
if (!client) return;
|
|
13906
|
-
async function membershipRequest(
|
|
13907
|
+
async function membershipRequest(path15, method = "GET", body) {
|
|
13907
13908
|
const headers = await client.buildHeaders();
|
|
13908
|
-
const response = await fetch(`${MEMBERSHIP_BASE}${
|
|
13909
|
+
const response = await fetch(`${MEMBERSHIP_BASE}${path15}`, {
|
|
13909
13910
|
method,
|
|
13910
13911
|
headers,
|
|
13911
13912
|
body: body ? JSON.stringify(body) : void 0
|
|
13912
13913
|
});
|
|
13913
13914
|
if (!response.ok) {
|
|
13914
13915
|
const text2 = await response.text();
|
|
13915
|
-
throw new Error(`Membership API Error ${response.status}: ${method} ${
|
|
13916
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path15}
|
|
13916
13917
|
${text2}`);
|
|
13917
13918
|
}
|
|
13918
13919
|
const text = await response.text();
|
|
@@ -14081,7 +14082,7 @@ var import_zod52 = require("zod");
|
|
|
14081
14082
|
var fs7 = __toESM(require("fs"));
|
|
14082
14083
|
var path6 = __toESM(require("path"));
|
|
14083
14084
|
function delay3(ms) {
|
|
14084
|
-
return new Promise((
|
|
14085
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
14085
14086
|
}
|
|
14086
14087
|
var TemplateSchema = import_zod52.z.object({
|
|
14087
14088
|
templateName: import_zod52.z.string(),
|
|
@@ -14225,7 +14226,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
14225
14226
|
const locId = client.resolveLocationId(locationId2);
|
|
14226
14227
|
const safePath = validateTemplatePath(templateFile);
|
|
14227
14228
|
const template = TemplateSchema.parse(JSON.parse(fs7.readFileSync(safePath, "utf-8")));
|
|
14228
|
-
const
|
|
14229
|
+
const resolve8 = (text) => {
|
|
14229
14230
|
if (typeof text !== "string") return text;
|
|
14230
14231
|
let result = text;
|
|
14231
14232
|
for (const [key, value] of Object.entries(answers)) {
|
|
@@ -14238,7 +14239,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
14238
14239
|
return result;
|
|
14239
14240
|
};
|
|
14240
14241
|
const resolveObj = (obj) => {
|
|
14241
|
-
if (typeof obj === "string") return
|
|
14242
|
+
if (typeof obj === "string") return resolve8(obj);
|
|
14242
14243
|
if (Array.isArray(obj)) return obj.map(resolveObj);
|
|
14243
14244
|
if (obj && typeof obj === "object") {
|
|
14244
14245
|
const result = {};
|
|
@@ -15239,6 +15240,57 @@ function registerDiagnosticTools(server2, installedVersion, client, builderClien
|
|
|
15239
15240
|
);
|
|
15240
15241
|
}
|
|
15241
15242
|
|
|
15243
|
+
// src/tools/user-guide.ts
|
|
15244
|
+
var import_child_process2 = require("child_process");
|
|
15245
|
+
var fs8 = __toESM(require("fs"));
|
|
15246
|
+
var path7 = __toESM(require("path"));
|
|
15247
|
+
function resolveGuidePath() {
|
|
15248
|
+
const bundled = path7.resolve(__dirname, "..", "guide", "guide.html");
|
|
15249
|
+
if (fs8.existsSync(bundled)) return bundled;
|
|
15250
|
+
const devRoot = path7.resolve(__dirname, "..", "..");
|
|
15251
|
+
try {
|
|
15252
|
+
const pkg2 = JSON.parse(fs8.readFileSync(path7.join(devRoot, "package.json"), "utf8"));
|
|
15253
|
+
if (pkg2.name === "@elitedcs/ghl-mcp") {
|
|
15254
|
+
const dev = path7.join(devRoot, "guide", "guide.html");
|
|
15255
|
+
if (fs8.existsSync(dev)) return dev;
|
|
15256
|
+
}
|
|
15257
|
+
} catch {
|
|
15258
|
+
}
|
|
15259
|
+
return bundled;
|
|
15260
|
+
}
|
|
15261
|
+
function registerUserGuideTools(server2) {
|
|
15262
|
+
server2.tool(
|
|
15263
|
+
"get_user_guide",
|
|
15264
|
+
"Open the GHL Command user guide in your browser \u2014 the full library of plain-English guides with copy-paste prompts (count and report contacts, build a nurture sequence, audit workflows, and more). Ships with every install, free or paid, and updates with the product. Always returns the guide's file path so it can be opened manually if no browser window appears.",
|
|
15265
|
+
{},
|
|
15266
|
+
async () => {
|
|
15267
|
+
const guidePath = resolveGuidePath();
|
|
15268
|
+
if (!fs8.existsSync(guidePath)) {
|
|
15269
|
+
return {
|
|
15270
|
+
content: [{
|
|
15271
|
+
type: "text",
|
|
15272
|
+
text: `The bundled user guide is missing (expected at ${guidePath}). Installed copies always include it \u2014 a reinstall restores it: npx -y @elitedcs/ghl-mcp cli install. In a development checkout, render it with: node scripts/build-guides.mjs`
|
|
15273
|
+
}]
|
|
15274
|
+
};
|
|
15275
|
+
}
|
|
15276
|
+
let launched = false;
|
|
15277
|
+
try {
|
|
15278
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
15279
|
+
const args = process.platform === "win32" ? ["/c", "start", "", guidePath] : [guidePath];
|
|
15280
|
+
(0, import_child_process2.spawn)(opener, args, { stdio: "ignore", detached: true }).unref();
|
|
15281
|
+
launched = true;
|
|
15282
|
+
} catch {
|
|
15283
|
+
}
|
|
15284
|
+
const text = launched ? `Opened the GHL Command user guide in your browser.
|
|
15285
|
+
|
|
15286
|
+
If a tab didn't appear, open this file directly:
|
|
15287
|
+
${guidePath}` : `Could not launch a browser automatically. Open this file directly:
|
|
15288
|
+
${guidePath}`;
|
|
15289
|
+
return { content: [{ type: "text", text }] };
|
|
15290
|
+
}
|
|
15291
|
+
);
|
|
15292
|
+
}
|
|
15293
|
+
|
|
15242
15294
|
// src/tools/snapshots.ts
|
|
15243
15295
|
var import_zod54 = require("zod");
|
|
15244
15296
|
init_ghl_client();
|
|
@@ -15921,9 +15973,9 @@ function presetForBusinessType(type) {
|
|
|
15921
15973
|
return "generic";
|
|
15922
15974
|
}
|
|
15923
15975
|
}
|
|
15924
|
-
function setPath(target,
|
|
15976
|
+
function setPath(target, path15, value) {
|
|
15925
15977
|
if (value === void 0) return;
|
|
15926
|
-
const parts =
|
|
15978
|
+
const parts = path15.split(".");
|
|
15927
15979
|
let node = target;
|
|
15928
15980
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
15929
15981
|
const k = parts[i];
|
|
@@ -18009,15 +18061,15 @@ function extractFunnelId(result) {
|
|
|
18009
18061
|
return void 0;
|
|
18010
18062
|
}
|
|
18011
18063
|
function makeExecuteDeps(client, builderClient, locationId2) {
|
|
18012
|
-
const pipelineApi = async (method,
|
|
18064
|
+
const pipelineApi = async (method, path15, body) => {
|
|
18013
18065
|
const headers = await builderClient.buildHeaders();
|
|
18014
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
18066
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path15}`;
|
|
18015
18067
|
const options = { method, headers };
|
|
18016
18068
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
18017
18069
|
const response = await fetch(url, options);
|
|
18018
18070
|
if (!response.ok) {
|
|
18019
18071
|
const text2 = await response.text();
|
|
18020
|
-
throw new Error(`Pipeline API ${response.status}: ${method} ${
|
|
18072
|
+
throw new Error(`Pipeline API ${response.status}: ${method} ${path15}
|
|
18021
18073
|
${text2.slice(0, 300)}`);
|
|
18022
18074
|
}
|
|
18023
18075
|
const text = await response.text();
|
|
@@ -18028,17 +18080,17 @@ ${text2.slice(0, 300)}`);
|
|
|
18028
18080
|
return JSON.parse(text.replace(/[\x00-\x1F\x7F]/g, ""));
|
|
18029
18081
|
}
|
|
18030
18082
|
};
|
|
18031
|
-
const funnelApi = async (method,
|
|
18083
|
+
const funnelApi = async (method, path15, body) => {
|
|
18032
18084
|
const headers = await builderClient.buildHeaders();
|
|
18033
18085
|
headers.Origin = "https://app.gohighlevel.com";
|
|
18034
18086
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
18035
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
18087
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path15}`;
|
|
18036
18088
|
const options = { method, headers };
|
|
18037
18089
|
if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
|
|
18038
18090
|
const response = await fetch(url, options);
|
|
18039
18091
|
if (!response.ok) {
|
|
18040
18092
|
const text2 = await response.text();
|
|
18041
|
-
throw new Error(`Funnel API ${response.status}: ${method} ${
|
|
18093
|
+
throw new Error(`Funnel API ${response.status}: ${method} ${path15}
|
|
18042
18094
|
${text2.slice(0, 300)}`);
|
|
18043
18095
|
}
|
|
18044
18096
|
const text = await response.text();
|
|
@@ -18689,12 +18741,14 @@ var SNAPSHOTS_MODULE = "snapshots";
|
|
|
18689
18741
|
var FORM_BUILDER_MODULE = "form-builder";
|
|
18690
18742
|
var INTAKE_TO_BUILD_MODULE = "intake-to-build";
|
|
18691
18743
|
var FUNNEL_QA_MODULE = "funnel-qa";
|
|
18744
|
+
var USER_GUIDE_MODULE = "user-guide";
|
|
18692
18745
|
var KNOWN_MODULES = /* @__PURE__ */ new Set([
|
|
18693
18746
|
...publicApiTools.map(([, label]) => label),
|
|
18694
18747
|
...internalApiTools.map(([, label]) => label),
|
|
18695
18748
|
FORM_BUILDER_MODULE,
|
|
18696
18749
|
INTAKE_TO_BUILD_MODULE,
|
|
18697
18750
|
FUNNEL_QA_MODULE,
|
|
18751
|
+
USER_GUIDE_MODULE,
|
|
18698
18752
|
VALIDATORS_MODULE,
|
|
18699
18753
|
DIAGNOSTICS_MODULE,
|
|
18700
18754
|
LOCATION_SWITCHER_MODULE,
|
|
@@ -18716,6 +18770,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
|
|
|
18716
18770
|
registerFormBuilderTools(wrap(FORM_BUILDER_MODULE), builderClient, client);
|
|
18717
18771
|
registerIntakeToBuildTools(wrap(INTAKE_TO_BUILD_MODULE), client, builderClient);
|
|
18718
18772
|
registerFunnelQaTools(wrap(FUNNEL_QA_MODULE), client, builderClient);
|
|
18773
|
+
registerUserGuideTools(wrap(USER_GUIDE_MODULE));
|
|
18719
18774
|
registerValidatorTools(wrap(VALIDATORS_MODULE), client, builderClient);
|
|
18720
18775
|
registerDiagnosticTools(
|
|
18721
18776
|
wrap(DIAGNOSTICS_MODULE),
|
|
@@ -18778,16 +18833,16 @@ var import_zod60 = require("zod");
|
|
|
18778
18833
|
|
|
18779
18834
|
// src/skill-installer.ts
|
|
18780
18835
|
var import_node_crypto2 = require("node:crypto");
|
|
18781
|
-
var
|
|
18782
|
-
var
|
|
18836
|
+
var fs9 = __toESM(require("node:fs"));
|
|
18837
|
+
var path8 = __toESM(require("node:path"));
|
|
18783
18838
|
var os3 = __toESM(require("node:os"));
|
|
18784
18839
|
function sha256(buf) {
|
|
18785
18840
|
return (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
|
|
18786
18841
|
}
|
|
18787
18842
|
function bundledSkillsDir(baseDir) {
|
|
18788
|
-
const candidate =
|
|
18843
|
+
const candidate = path8.resolve(baseDir, "..", "skills");
|
|
18789
18844
|
try {
|
|
18790
|
-
return
|
|
18845
|
+
return fs9.statSync(candidate).isDirectory() ? candidate : null;
|
|
18791
18846
|
} catch {
|
|
18792
18847
|
return null;
|
|
18793
18848
|
}
|
|
@@ -18795,10 +18850,10 @@ function bundledSkillsDir(baseDir) {
|
|
|
18795
18850
|
function listFiles(root) {
|
|
18796
18851
|
const out = [];
|
|
18797
18852
|
const walk = (dir) => {
|
|
18798
|
-
for (const entry of
|
|
18799
|
-
const full =
|
|
18853
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
18854
|
+
const full = path8.join(dir, entry.name);
|
|
18800
18855
|
if (entry.isDirectory()) walk(full);
|
|
18801
|
-
else if (entry.isFile()) out.push(
|
|
18856
|
+
else if (entry.isFile()) out.push(path8.relative(root, full));
|
|
18802
18857
|
}
|
|
18803
18858
|
};
|
|
18804
18859
|
walk(root);
|
|
@@ -18806,7 +18861,7 @@ function listFiles(root) {
|
|
|
18806
18861
|
}
|
|
18807
18862
|
function readMarker(markerPath) {
|
|
18808
18863
|
try {
|
|
18809
|
-
const parsed = JSON.parse(
|
|
18864
|
+
const parsed = JSON.parse(fs9.readFileSync(markerPath, "utf8"));
|
|
18810
18865
|
return parsed && typeof parsed === "object" && parsed.files ? parsed : null;
|
|
18811
18866
|
} catch {
|
|
18812
18867
|
return null;
|
|
@@ -18819,35 +18874,35 @@ function installBundledSkills(opts) {
|
|
|
18819
18874
|
skippedUserModified: [],
|
|
18820
18875
|
unchanged: [],
|
|
18821
18876
|
errors: [],
|
|
18822
|
-
targetDir: opts.targetDir ??
|
|
18877
|
+
targetDir: opts.targetDir ?? path8.join(os3.homedir(), ".claude", "skills"),
|
|
18823
18878
|
bundledDir: null
|
|
18824
18879
|
};
|
|
18825
18880
|
const bundled = bundledSkillsDir(opts.baseDir);
|
|
18826
18881
|
result.bundledDir = bundled;
|
|
18827
18882
|
if (!bundled) return result;
|
|
18828
18883
|
try {
|
|
18829
|
-
|
|
18884
|
+
fs9.mkdirSync(result.targetDir, { recursive: true });
|
|
18830
18885
|
} catch (e) {
|
|
18831
18886
|
result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
|
|
18832
18887
|
return result;
|
|
18833
18888
|
}
|
|
18834
|
-
const markerPath =
|
|
18889
|
+
const markerPath = path8.join(result.targetDir, ".ghl-command-skills.json");
|
|
18835
18890
|
const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
|
|
18836
18891
|
const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
|
|
18837
18892
|
for (const rel of listFiles(bundled)) {
|
|
18838
18893
|
try {
|
|
18839
|
-
const src =
|
|
18840
|
-
const dest =
|
|
18894
|
+
const src = fs9.readFileSync(path8.join(bundled, rel));
|
|
18895
|
+
const dest = path8.join(result.targetDir, rel);
|
|
18841
18896
|
const srcHash = sha256(src);
|
|
18842
18897
|
let destBuf = null;
|
|
18843
18898
|
try {
|
|
18844
|
-
destBuf =
|
|
18899
|
+
destBuf = fs9.readFileSync(dest);
|
|
18845
18900
|
} catch {
|
|
18846
18901
|
destBuf = null;
|
|
18847
18902
|
}
|
|
18848
18903
|
if (destBuf === null) {
|
|
18849
|
-
|
|
18850
|
-
|
|
18904
|
+
fs9.mkdirSync(path8.dirname(dest), { recursive: true });
|
|
18905
|
+
fs9.writeFileSync(dest, src);
|
|
18851
18906
|
newMarker.files[rel] = srcHash;
|
|
18852
18907
|
result.installed.push(rel);
|
|
18853
18908
|
continue;
|
|
@@ -18860,7 +18915,7 @@ function installBundledSkills(opts) {
|
|
|
18860
18915
|
}
|
|
18861
18916
|
const lastWritten = marker.files[rel];
|
|
18862
18917
|
if (lastWritten && destHash === lastWritten) {
|
|
18863
|
-
|
|
18918
|
+
fs9.writeFileSync(dest, src);
|
|
18864
18919
|
newMarker.files[rel] = srcHash;
|
|
18865
18920
|
result.updated.push(rel);
|
|
18866
18921
|
} else {
|
|
@@ -18871,7 +18926,7 @@ function installBundledSkills(opts) {
|
|
|
18871
18926
|
}
|
|
18872
18927
|
}
|
|
18873
18928
|
try {
|
|
18874
|
-
|
|
18929
|
+
fs9.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
|
|
18875
18930
|
} catch (e) {
|
|
18876
18931
|
result.errors.push(`marker write failed: ${String(e)}`);
|
|
18877
18932
|
}
|
|
@@ -18956,8 +19011,8 @@ function registerMetaTools(server2, installedVersion) {
|
|
|
18956
19011
|
|
|
18957
19012
|
// src/cli.ts
|
|
18958
19013
|
var import_node_util2 = require("node:util");
|
|
18959
|
-
var
|
|
18960
|
-
var
|
|
19014
|
+
var fs11 = __toESM(require("fs"));
|
|
19015
|
+
var path10 = __toESM(require("path"));
|
|
18961
19016
|
var import_crypto2 = require("crypto");
|
|
18962
19017
|
init_ghl_client();
|
|
18963
19018
|
init_token_registry();
|
|
@@ -19008,9 +19063,9 @@ function errLine(msg2) {
|
|
|
19008
19063
|
function preflightWritable() {
|
|
19009
19064
|
try {
|
|
19010
19065
|
const dir = ensureAppDataDir();
|
|
19011
|
-
const probe =
|
|
19012
|
-
|
|
19013
|
-
|
|
19066
|
+
const probe = path10.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
|
|
19067
|
+
fs11.writeFileSync(probe, "ok");
|
|
19068
|
+
fs11.unlinkSync(probe);
|
|
19014
19069
|
return true;
|
|
19015
19070
|
} catch (error) {
|
|
19016
19071
|
errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -19264,7 +19319,7 @@ var bundledPkg = require_package();
|
|
|
19264
19319
|
var pkg = (() => {
|
|
19265
19320
|
try {
|
|
19266
19321
|
const onDisk = JSON.parse(
|
|
19267
|
-
|
|
19322
|
+
fs15.readFileSync(path14.resolve(__dirname, "..", "package.json"), "utf8")
|
|
19268
19323
|
);
|
|
19269
19324
|
if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
|
|
19270
19325
|
return { version: onDisk.version };
|
|
@@ -19277,7 +19332,7 @@ dotenv2.config();
|
|
|
19277
19332
|
setPkgVersion(pkg.version);
|
|
19278
19333
|
{
|
|
19279
19334
|
const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
19280
|
-
if (configDirOverride && !
|
|
19335
|
+
if (configDirOverride && !path14.isAbsolute(configDirOverride)) {
|
|
19281
19336
|
process.stderr.write(
|
|
19282
19337
|
`[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.
|
|
19283
19338
|
`
|
|
@@ -19290,20 +19345,20 @@ process.on("unhandledRejection", (reason) => {
|
|
|
19290
19345
|
`);
|
|
19291
19346
|
});
|
|
19292
19347
|
function hardenSecretFilePerms() {
|
|
19293
|
-
const repoDir =
|
|
19348
|
+
const repoDir = path14.resolve(__dirname, "..");
|
|
19294
19349
|
const candidates = [
|
|
19295
|
-
{ file:
|
|
19350
|
+
{ file: path14.join(repoDir, "start-mcp.sh"), mode: 448 },
|
|
19296
19351
|
// Legacy registry location (pre-migration); new location lives in app-data.
|
|
19297
|
-
{ file:
|
|
19352
|
+
{ file: path14.join(repoDir, ".ghl-tokens.json"), mode: 384 },
|
|
19298
19353
|
{ file: tokenRegistryPath(), mode: 384 }
|
|
19299
19354
|
];
|
|
19300
19355
|
for (const { file, mode } of candidates) {
|
|
19301
19356
|
let current;
|
|
19302
19357
|
try {
|
|
19303
|
-
if (!
|
|
19304
|
-
current =
|
|
19358
|
+
if (!fs15.existsSync(file)) continue;
|
|
19359
|
+
current = fs15.statSync(file).mode & 511;
|
|
19305
19360
|
if (current !== mode) {
|
|
19306
|
-
|
|
19361
|
+
fs15.chmodSync(file, mode);
|
|
19307
19362
|
}
|
|
19308
19363
|
} catch (error) {
|
|
19309
19364
|
const message = error instanceof Error ? error.message : String(error);
|