@elitedcs/ghl-mcp 3.83.5 → 3.83.7
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 +51 -0
- package/README.md +1 -1
- package/dist/index.js +92 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 3.83.7 — the cockpit answers only to its own address, and keeps its key to itself
|
|
6
|
+
|
|
7
|
+
**Security fix. Update to this version.**
|
|
8
|
+
|
|
9
|
+
The cockpit runs on your own computer and only accepts connections from it. Two
|
|
10
|
+
things it should have refused, it did not:
|
|
11
|
+
|
|
12
|
+
- **A design run's details were readable without the cockpit's key.** Anything
|
|
13
|
+
able to reach the cockpit on your computer could list the Design Studio runs it
|
|
14
|
+
had started: the brand, headline, points and audience you typed, the run's log,
|
|
15
|
+
and the folder it saved to, which includes your computer's user name.
|
|
16
|
+
- **The cockpit handed its key to a request that used a different address for
|
|
17
|
+
it.** A web page that tricks your browser into reaching the cockpit under
|
|
18
|
+
another name could have picked up that key, and with it read what the cockpit
|
|
19
|
+
reads for your clients: intake answers, uploaded intake documents, what each
|
|
20
|
+
account already has set up, and your snapshot list. Nothing could be changed
|
|
21
|
+
that way; every change the cockpit makes kept refusing such requests. No
|
|
22
|
+
GoHighLevel key was exposed by either path.
|
|
23
|
+
|
|
24
|
+
Now the cockpit refuses any request that is not addressed to `localhost` or
|
|
25
|
+
`127.0.0.1` on its own port, before it does anything else. It gives its key only
|
|
26
|
+
to its own pages opened on your computer, never to anything else. Design Studio's
|
|
27
|
+
run list and progress need the key, like the cockpit's other private reads. And a
|
|
28
|
+
request that spells a page's address in an unusual way is refused rather than
|
|
29
|
+
guessed at.
|
|
30
|
+
|
|
31
|
+
Nothing changes in how you open it: the address the cockpit opens for you is one of
|
|
32
|
+
the two it accepts.
|
|
33
|
+
|
|
34
|
+
## 3.83.6 — what a design run actually costs you, measured
|
|
35
|
+
|
|
36
|
+
3.83.5 gave a running job a clock. This gives it an honest number on it.
|
|
37
|
+
|
|
38
|
+
How long a run takes was written by hand in three places and every one was wrong.
|
|
39
|
+
The brand page said a deck takes "roughly ten minutes". The run API said "twenty
|
|
40
|
+
minutes or more". The clock added in 3.83.5 said "ten to twenty". Nobody had ever
|
|
41
|
+
timed one.
|
|
42
|
+
|
|
43
|
+
Two runs have now been measured. A six-slide deck took 12 minutes 39 seconds and
|
|
44
|
+
$1.74 for a single pass. A conversion page on a real brand took 26 minutes and
|
|
45
|
+
$3.75 across three. So a pass is 9 to 13 minutes and $1.25 to $1.75, and a run may
|
|
46
|
+
use three of them.
|
|
47
|
+
|
|
48
|
+
That sentence now lives in one file and every screen reads it, so the three cannot
|
|
49
|
+
drift apart again.
|
|
50
|
+
|
|
51
|
+
**The cost is now stated before you press the button, not discovered afterwards.**
|
|
52
|
+
These runs use your own Claude, never ours. On a Max plan that is usage rather than
|
|
53
|
+
a bill, which is exactly why the figure belongs on the screen: on pay as you go it
|
|
54
|
+
is real money, and you should know roughly what a button costs before you press it.
|
|
55
|
+
|
|
5
56
|
## 3.83.5 — the brand page's buttons work, and a run tells you it is working
|
|
6
57
|
|
|
7
58
|
If you use Design Studio, every button on a brand's own page has been dead since
|
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -27187,6 +27187,20 @@ var init_run_jobs = __esm({
|
|
|
27187
27187
|
}
|
|
27188
27188
|
});
|
|
27189
27189
|
|
|
27190
|
+
// src/command-os/design/run-expectations.ts
|
|
27191
|
+
var RUN_MAX_PASSES, RUN_PASS_MINUTES, RUN_PASS_COST, RUN_TOTAL_MINUTES, RUN_TOTAL_COST, RUN_EXPECTATION;
|
|
27192
|
+
var init_run_expectations = __esm({
|
|
27193
|
+
"src/command-os/design/run-expectations.ts"() {
|
|
27194
|
+
"use strict";
|
|
27195
|
+
RUN_MAX_PASSES = 3;
|
|
27196
|
+
RUN_PASS_MINUTES = "9 to 13 minutes";
|
|
27197
|
+
RUN_PASS_COST = "$1.25 to $1.75";
|
|
27198
|
+
RUN_TOTAL_MINUTES = "25 to 40 minutes";
|
|
27199
|
+
RUN_TOTAL_COST = "about $5";
|
|
27200
|
+
RUN_EXPECTATION = `Each pass takes ${RUN_PASS_MINUTES} and costs ${RUN_PASS_COST} of your own Claude usage, and a run may take up to ${RUN_MAX_PASSES} passes. Allow ${RUN_TOTAL_MINUTES} and up to ${RUN_TOTAL_COST}.`;
|
|
27201
|
+
}
|
|
27202
|
+
});
|
|
27203
|
+
|
|
27190
27204
|
// src/run-cost.ts
|
|
27191
27205
|
function runCostPath() {
|
|
27192
27206
|
return path18.join(appDataDir(), "command-os-run-cost.jsonl");
|
|
@@ -28591,7 +28605,7 @@ var require_package = __commonJS({
|
|
|
28591
28605
|
"package.json"(exports2, module2) {
|
|
28592
28606
|
module2.exports = {
|
|
28593
28607
|
name: "@elitedcs/ghl-mcp",
|
|
28594
|
-
version: "3.83.
|
|
28608
|
+
version: "3.83.7",
|
|
28595
28609
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
28596
28610
|
description: "GoHighLevel MCP Server for Claude. 250 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.",
|
|
28597
28611
|
main: "dist/index.js",
|
|
@@ -32369,7 +32383,7 @@ ${d.voice.samples.length ? `<h2>Voice</h2><ul>${d.voice.samples.map((s) => `<li>
|
|
|
32369
32383
|
</div>
|
|
32370
32384
|
${opts.screenshot ? `<h2>Their site, as rendered</h2><img class="shot" src="/brand/${encodeURIComponent(d.slug)}/screenshot.png" alt="Screenshot of ${esc5(d.name)}'s website">` : ""}
|
|
32371
32385
|
<h2>Make something in this brand</h2>
|
|
32372
|
-
<p class="small">Runs on <b>your own Claude</b>, on this computer
|
|
32386
|
+
<p class="small">Runs on <b>your own Claude</b>, on this computer, never our key. ${esc5(RUN_EXPECTATION)} The run keeps going if you close this tab. Write the headline and the points you want made, not instructions.</p>
|
|
32373
32387
|
<form id="make"><div class="two">
|
|
32374
32388
|
<div><label for="mk-medium">What</label><select id="mk-medium"><option value="deck">A deck</option><option value="site">A landing page</option><option value="report">A report</option></select></div>
|
|
32375
32389
|
<div><label for="mk-slides">Slides (a deck only)</label><input id="mk-slides" type="number" min="1" max="40" value="6"></div>
|
|
@@ -32398,6 +32412,7 @@ ${opts.dir ? `<p class="small">Stored at ${esc5(opts.dir)}</p>` : ""}
|
|
|
32398
32412
|
${foot(SHELL_JS(opts.seatToken ?? "") + `
|
|
32399
32413
|
(function(){
|
|
32400
32414
|
var poll=null,runId=null,started=0,ticker=null,passes=0;
|
|
32415
|
+
var EXPECT=${scriptJson(RUN_EXPECTATION)};
|
|
32401
32416
|
function stopPolling(){if(poll){clearInterval(poll);poll=null}if(ticker){clearInterval(ticker);ticker=null}}
|
|
32402
32417
|
function elapsed(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60);s=s%60;return m?(m+"m "+(s<10?"0":"")+s+"s"):(s+"s")}
|
|
32403
32418
|
/* The run prints NOTHING while Claude writes - it answers in one go, so the log
|
|
@@ -32407,7 +32422,7 @@ function elapsed(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60);s=s%60;return
|
|
|
32407
32422
|
from the child process, and it ticks every second, not every poll. */
|
|
32408
32423
|
function tick(){
|
|
32409
32424
|
if(!started)return;
|
|
32410
|
-
say("mk-status","Working on your Claude: "+elapsed(Date.now()-started)+" so far"+(passes?(", on pass "+(passes+1)):"")+". Claude writes the whole file in one answer, so nothing appears below until a pass finishes.
|
|
32425
|
+
say("mk-status","Working on your Claude: "+elapsed(Date.now()-started)+" so far"+(passes?(", on pass "+(passes+1)):"")+". Claude writes the whole file in one answer, so nothing appears below until a pass finishes. "+EXPECT+" Closing this tab does not stop it.");
|
|
32411
32426
|
}
|
|
32412
32427
|
function show(t){var el=document.getElementById("mk-log");el.hidden=false;el.textContent=t}
|
|
32413
32428
|
document.getElementById("make").addEventListener("submit",async function(e){
|
|
@@ -32427,7 +32442,7 @@ document.getElementById("make").addEventListener("submit",async function(e){
|
|
|
32427
32442
|
document.getElementById("mk-cancel").hidden=false;
|
|
32428
32443
|
started=Date.now();passes=0;tick();ticker=setInterval(tick,1000);
|
|
32429
32444
|
poll=setInterval(async function(){
|
|
32430
|
-
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId))).json();
|
|
32445
|
+
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId),{headers:{"x-command-os-seat":KEY}})).json();
|
|
32431
32446
|
var lines=s.tail||[];
|
|
32432
32447
|
show(lines.join("\\n"));
|
|
32433
32448
|
passes=lines.filter(function(l){return l.trim().slice(0,5)==="pass "}).length;
|
|
@@ -32453,6 +32468,7 @@ var esc5, STYLE, SHELL_JS, foot;
|
|
|
32453
32468
|
var init_brand_page = __esm({
|
|
32454
32469
|
"src/command-os/design/brand-page.ts"() {
|
|
32455
32470
|
"use strict";
|
|
32471
|
+
init_run_expectations();
|
|
32456
32472
|
init_fonts();
|
|
32457
32473
|
init_cockpit_shell();
|
|
32458
32474
|
init_script_json();
|
|
@@ -33969,9 +33985,11 @@ __export(dashboard_exports, {
|
|
|
33969
33985
|
insideDir: () => insideDir,
|
|
33970
33986
|
intakeInput: () => intakeInput,
|
|
33971
33987
|
intakeRequestError: () => intakeRequestError,
|
|
33988
|
+
isAllowedCockpitHost: () => isAllowedCockpitHost,
|
|
33972
33989
|
isLoopback: () => isLoopback,
|
|
33973
33990
|
launcherAlreadyOffered: () => launcherAlreadyOffered,
|
|
33974
33991
|
listClientDocs: () => listClientDocs,
|
|
33992
|
+
mayServeSeat: () => mayServeSeat,
|
|
33975
33993
|
memberFacingError: () => memberFacingError,
|
|
33976
33994
|
newClientFailure: () => newClientFailure,
|
|
33977
33995
|
normalizedIntake: () => normalizedIntake,
|
|
@@ -35736,22 +35754,45 @@ function checkSeatToken(req, expected) {
|
|
|
35736
35754
|
}
|
|
35737
35755
|
return null;
|
|
35738
35756
|
}
|
|
35739
|
-
function
|
|
35740
|
-
const
|
|
35741
|
-
|
|
35742
|
-
|
|
35743
|
-
|
|
35744
|
-
|
|
35745
|
-
}
|
|
35746
|
-
|
|
35747
|
-
|
|
35748
|
-
|
|
35757
|
+
function cockpitHeader(req, name) {
|
|
35758
|
+
const value = req.headers[name];
|
|
35759
|
+
return Array.isArray(value) ? value.join(",") : value ?? "";
|
|
35760
|
+
}
|
|
35761
|
+
function isAllowedCockpitHost(req, port) {
|
|
35762
|
+
const host = cockpitHeader(req, "host").toLowerCase();
|
|
35763
|
+
return host === `127.0.0.1:${port}` || host === `localhost:${port}`;
|
|
35764
|
+
}
|
|
35765
|
+
function mayServeSeat(req, port) {
|
|
35766
|
+
return isLoopback(req) && isAllowedCockpitHost(req, port);
|
|
35767
|
+
}
|
|
35768
|
+
function cockpitPath(req) {
|
|
35769
|
+
return (req.url || "/").split("?")[0];
|
|
35770
|
+
}
|
|
35771
|
+
function checkCockpitPath(req) {
|
|
35772
|
+
const pathname = cockpitPath(req);
|
|
35773
|
+
if (!pathname.startsWith("/") || /\/\/|\\|#|[\x00-\x20\x7f]|%(?:2e|2f|5c)|%(?![0-9a-f]{2})/i.test(pathname) || pathname.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
35774
|
+
return { status: 400, error: "Use a canonical cockpit path." };
|
|
35749
35775
|
}
|
|
35750
|
-
|
|
35751
|
-
|
|
35776
|
+
return null;
|
|
35777
|
+
}
|
|
35778
|
+
function checkCockpitOrigin(req, port) {
|
|
35779
|
+
const origin = cockpitHeader(req, "origin").toLowerCase();
|
|
35780
|
+
if (req.headers.origin !== void 0 && origin !== `http://127.0.0.1:${port}` && origin !== `http://localhost:${port}`) {
|
|
35752
35781
|
return { status: 403, error: "cross-origin request rejected" };
|
|
35753
35782
|
}
|
|
35754
|
-
|
|
35783
|
+
return null;
|
|
35784
|
+
}
|
|
35785
|
+
function checkMutationGuard(req, port) {
|
|
35786
|
+
if (!isAllowedCockpitHost(req, port)) {
|
|
35787
|
+
return { status: 403, error: "This Host is not allowed to access the cockpit." };
|
|
35788
|
+
}
|
|
35789
|
+
const pathDenied = checkCockpitPath(req);
|
|
35790
|
+
if (pathDenied) return pathDenied;
|
|
35791
|
+
const method = (req.method || "GET").toUpperCase();
|
|
35792
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return null;
|
|
35793
|
+
const originDenied = checkCockpitOrigin(req, port);
|
|
35794
|
+
if (originDenied) return originDenied;
|
|
35795
|
+
if (cockpitHeader(req, "content-type").split(";")[0].trim().toLowerCase() !== "application/json") {
|
|
35755
35796
|
return { status: 415, error: "expected application/json" };
|
|
35756
35797
|
}
|
|
35757
35798
|
return null;
|
|
@@ -35759,8 +35800,13 @@ function checkMutationGuard(req, port) {
|
|
|
35759
35800
|
function cockpitGate(req, seatToken, port) {
|
|
35760
35801
|
const denied = checkMutationGuard(req, port) ?? checkSeatToken(req, seatToken);
|
|
35761
35802
|
if (denied) return denied;
|
|
35762
|
-
const pathname = (req
|
|
35763
|
-
if (SEAT_GUARDED_READS.includes(pathname)
|
|
35803
|
+
const pathname = cockpitPath(req);
|
|
35804
|
+
if (SEAT_GUARDED_READS.includes(pathname) || pathname === "/api/design" || pathname.startsWith("/api/design/")) {
|
|
35805
|
+
if (cockpitHeader(req, "sec-fetch-site").toLowerCase() === "cross-site") {
|
|
35806
|
+
return { status: 403, error: "cross-site request rejected" };
|
|
35807
|
+
}
|
|
35808
|
+
return checkCockpitOrigin(req, port) ?? checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
35809
|
+
}
|
|
35764
35810
|
return null;
|
|
35765
35811
|
}
|
|
35766
35812
|
function guarded(seatToken, port, routes) {
|
|
@@ -35885,7 +35931,7 @@ async function runDashboard(argv) {
|
|
|
35885
35931
|
questions: (id) => composeQuestionSet({ industry: readCockpitState().accounts?.[id]?.industry, agency: readOverlay() })
|
|
35886
35932
|
};
|
|
35887
35933
|
const server2 = http.createServer(guarded(seatToken, port, (req, res) => {
|
|
35888
|
-
const url = req
|
|
35934
|
+
const url = cockpitPath(req);
|
|
35889
35935
|
if (req.method === "POST" && url === "/api/client-assessment") {
|
|
35890
35936
|
readBody(req, res, 2e4, async (body) => {
|
|
35891
35937
|
try {
|
|
@@ -35907,10 +35953,10 @@ async function runDashboard(argv) {
|
|
|
35907
35953
|
return;
|
|
35908
35954
|
}
|
|
35909
35955
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
35910
|
-
res.end(html(currentRoster(),
|
|
35956
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", "for", cap));
|
|
35911
35957
|
return;
|
|
35912
35958
|
}
|
|
35913
|
-
if (req.method === "GET" && url
|
|
35959
|
+
if (req.method === "GET" && url === "/api/capability") {
|
|
35914
35960
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
35915
35961
|
const cap = capabilityByKey(u.searchParams.get("key") ?? "");
|
|
35916
35962
|
const locationId2 = u.searchParams.get("locationId") ?? "";
|
|
@@ -36045,7 +36091,7 @@ async function runDashboard(argv) {
|
|
|
36045
36091
|
});
|
|
36046
36092
|
return;
|
|
36047
36093
|
}
|
|
36048
|
-
if (req.method === "GET" && url
|
|
36094
|
+
if (req.method === "GET" && url === "/api/intake-documents") {
|
|
36049
36095
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
36050
36096
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36051
36097
|
if (seat) {
|
|
@@ -36093,7 +36139,7 @@ async function runDashboard(argv) {
|
|
|
36093
36139
|
const boardScreen = BOARD_ROUTES[url];
|
|
36094
36140
|
if (req.method === "GET" && boardScreen) {
|
|
36095
36141
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36096
|
-
res.end(html(currentRoster(),
|
|
36142
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", boardScreen));
|
|
36097
36143
|
return;
|
|
36098
36144
|
}
|
|
36099
36145
|
if ((req.method === "GET" || req.method === "HEAD") && (url === "/assessment" || url === "/assessment.html")) {
|
|
@@ -36105,7 +36151,7 @@ async function runDashboard(argv) {
|
|
|
36105
36151
|
"X-Robots-Tag": "noindex, nofollow"
|
|
36106
36152
|
});
|
|
36107
36153
|
const roster2 = currentRoster().map((r) => ({ locationId: r.locationId, name: r.name }));
|
|
36108
|
-
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(
|
|
36154
|
+
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(mayServeSeat(req, port) ? seatToken : "")};</script>`;
|
|
36109
36155
|
res.end(req.method === "HEAD" ? void 0 : withCockpitShell(ASSESSMENT_HTML, "assessment", assessmentAgencyTag() + clientsTag));
|
|
36110
36156
|
return;
|
|
36111
36157
|
}
|
|
@@ -36148,14 +36194,14 @@ async function runDashboard(argv) {
|
|
|
36148
36194
|
});
|
|
36149
36195
|
return;
|
|
36150
36196
|
}
|
|
36151
|
-
if (req.method === "GET" && url
|
|
36197
|
+
if (req.method === "GET" && url === "/api/client-preflight") {
|
|
36152
36198
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36153
36199
|
if (seat) {
|
|
36154
36200
|
res.writeHead(seat.status);
|
|
36155
36201
|
res.end(JSON.stringify({ ok: false, error: seat.error }));
|
|
36156
36202
|
return;
|
|
36157
36203
|
}
|
|
36158
|
-
const id = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36204
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36159
36205
|
if (!readableAccount(id)) {
|
|
36160
36206
|
res.writeHead(400);
|
|
36161
36207
|
res.end(JSON.stringify({ ok: false, error: "Choose a registered client." }));
|
|
@@ -36311,8 +36357,8 @@ async function runDashboard(argv) {
|
|
|
36311
36357
|
});
|
|
36312
36358
|
return;
|
|
36313
36359
|
}
|
|
36314
|
-
if (req.method === "GET" && url
|
|
36315
|
-
const industry = new URL(url, "http://x").searchParams.get("industry") || void 0;
|
|
36360
|
+
if (req.method === "GET" && url === "/api/intake") {
|
|
36361
|
+
const industry = new URL(req.url || "/", "http://x").searchParams.get("industry") || void 0;
|
|
36316
36362
|
const agency = readOverlay();
|
|
36317
36363
|
const composed = composeQuestionSet({ industry, agency });
|
|
36318
36364
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -36341,10 +36387,10 @@ async function runDashboard(argv) {
|
|
|
36341
36387
|
});
|
|
36342
36388
|
return;
|
|
36343
36389
|
}
|
|
36344
|
-
if (req.method === "GET" && url
|
|
36390
|
+
if (req.method === "GET" && url === "/api/client-intake") {
|
|
36345
36391
|
(async () => {
|
|
36346
36392
|
try {
|
|
36347
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36393
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36348
36394
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36349
36395
|
if (seat) {
|
|
36350
36396
|
res.writeHead(seat.status, { "Content-Type": "application/json" });
|
|
@@ -36368,7 +36414,7 @@ async function runDashboard(argv) {
|
|
|
36368
36414
|
res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q2) => q2.required && !String(answers[q2.label] ?? answers[q2.key] ?? "").trim()).map((q2) => q2.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
|
|
36369
36415
|
} catch (e) {
|
|
36370
36416
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36371
|
-
logQuietly(`client-intake ${new URL(url, "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36417
|
+
logQuietly(`client-intake ${new URL(req.url || "/", "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36372
36418
|
res.end(JSON.stringify({ ok: false, error: intakeRequestError(e) }));
|
|
36373
36419
|
}
|
|
36374
36420
|
})();
|
|
@@ -36492,12 +36538,12 @@ async function runDashboard(argv) {
|
|
|
36492
36538
|
if (!rows.some((r) => r.locationId === locationId2)) rows.push({ locationId: locationId2, name: e.clientName || "(no account connected)", engagement: e });
|
|
36493
36539
|
}
|
|
36494
36540
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36495
|
-
res.end(agencyProfilePageHtml(readAgencyProfile(), rows,
|
|
36541
|
+
res.end(agencyProfilePageHtml(readAgencyProfile(), rows, mayServeSeat(req, port) ? seatToken : ""));
|
|
36496
36542
|
return;
|
|
36497
36543
|
}
|
|
36498
36544
|
if (req.method === "GET" && url === "/brands") {
|
|
36499
36545
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36500
|
-
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(),
|
|
36546
|
+
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(), mayServeSeat(req, port) ? seatToken : ""));
|
|
36501
36547
|
return;
|
|
36502
36548
|
}
|
|
36503
36549
|
if (req.method === "GET" && /^\/brand\/[a-z0-9-]+\/screenshot\.png$/.test(url)) {
|
|
@@ -36518,7 +36564,7 @@ async function runDashboard(argv) {
|
|
|
36518
36564
|
const dna = houseLooks().find((d) => d.slug === slug3) ?? readBrand(slug3);
|
|
36519
36565
|
const dir = brandExists(slug3) ? brandDir(slug3) : void 0;
|
|
36520
36566
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36521
|
-
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken:
|
|
36567
|
+
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
|
|
36522
36568
|
} catch (e) {
|
|
36523
36569
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
36524
36570
|
res.end(e.message);
|
|
@@ -36536,7 +36582,7 @@ async function runDashboard(argv) {
|
|
|
36536
36582
|
}
|
|
36537
36583
|
const job = startDesignRun(checked.value);
|
|
36538
36584
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36539
|
-
res.end(JSON.stringify({ ok: true, id: job.id, outDir: job.outDir, note:
|
|
36585
|
+
res.end(JSON.stringify({ ok: true, id: job.id, outDir: job.outDir, note: `Started on your own Claude. ${RUN_EXPECTATION} Poll /api/design/run?id= for progress.` }));
|
|
36540
36586
|
} catch (e) {
|
|
36541
36587
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36542
36588
|
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
@@ -36544,8 +36590,8 @@ async function runDashboard(argv) {
|
|
|
36544
36590
|
});
|
|
36545
36591
|
return;
|
|
36546
36592
|
}
|
|
36547
|
-
if (req.method === "GET" && url
|
|
36548
|
-
const id = new URL(url, "http://x").searchParams.get("id");
|
|
36593
|
+
if (req.method === "GET" && url === "/api/design/run") {
|
|
36594
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("id");
|
|
36549
36595
|
const payload = id ? designRunStatus(id) : { runs: listDesignRuns() };
|
|
36550
36596
|
res.writeHead(payload ? 200 : 404, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
36551
36597
|
res.end(JSON.stringify(payload ?? { ok: false, error: "no such run" }));
|
|
@@ -36671,8 +36717,8 @@ async function runDashboard(argv) {
|
|
|
36671
36717
|
});
|
|
36672
36718
|
return;
|
|
36673
36719
|
}
|
|
36674
|
-
if (req.method === "GET" && url
|
|
36675
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36720
|
+
if (req.method === "GET" && url === "/handoff") {
|
|
36721
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36676
36722
|
if (!readableAccount(locationId2)) {
|
|
36677
36723
|
res.writeHead(400);
|
|
36678
36724
|
res.end("Choose a registered client.");
|
|
@@ -36775,8 +36821,8 @@ async function runDashboard(argv) {
|
|
|
36775
36821
|
});
|
|
36776
36822
|
return;
|
|
36777
36823
|
}
|
|
36778
|
-
if (req.method === "GET" && url
|
|
36779
|
-
const q2 = new URL(url, "http://x").searchParams;
|
|
36824
|
+
if (req.method === "GET" && url === "/blueprint") {
|
|
36825
|
+
const q2 = new URL(req.url || "/", "http://x").searchParams;
|
|
36780
36826
|
const locationId2 = q2.get("locationId") || "";
|
|
36781
36827
|
const st = readCockpitState();
|
|
36782
36828
|
const review = st.reviews?.[locationId2];
|
|
@@ -36790,8 +36836,8 @@ async function runDashboard(argv) {
|
|
|
36790
36836
|
res.end(reviewDocumentHtml(review, { assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: st.assessmentPurchases?.[locationId2], planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q2.get("agency") || st.agencyName, accent: q2.get("accent") || st.agencyAccent }));
|
|
36791
36837
|
return;
|
|
36792
36838
|
}
|
|
36793
|
-
if (req.method === "GET" && url
|
|
36794
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36839
|
+
if (req.method === "GET" && url === "/api/review") {
|
|
36840
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36795
36841
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36796
36842
|
res.end(JSON.stringify(readCockpitState().reviews?.[locationId2] ?? { ok: false, error: "not generated yet" }));
|
|
36797
36843
|
return;
|
|
@@ -37294,6 +37340,7 @@ var init_dashboard = __esm({
|
|
|
37294
37340
|
init_claude_cli();
|
|
37295
37341
|
init_token_registry();
|
|
37296
37342
|
init_run_jobs();
|
|
37343
|
+
init_run_expectations();
|
|
37297
37344
|
init_stage_runner();
|
|
37298
37345
|
init_account_rules();
|
|
37299
37346
|
init_claude_call();
|
|
@@ -37382,7 +37429,7 @@ var init_dashboard = __esm({
|
|
|
37382
37429
|
anthropicKeyWrites = Promise.resolve();
|
|
37383
37430
|
SEAT_TOKEN_HEADER = "x-command-os-seat";
|
|
37384
37431
|
NOT_SIGNED_IN_MSG = "This browser isn't signed in to Command OS on this computer. Open Command OS on the machine it runs on, and use the page it opens for you.";
|
|
37385
|
-
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots"];
|
|
37432
|
+
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight"];
|
|
37386
37433
|
}
|
|
37387
37434
|
});
|
|
37388
37435
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.83.
|
|
3
|
+
"version": "3.83.7",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 250 tools — 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.",
|
|
6
6
|
"main": "dist/index.js",
|