@elitedcs/ghl-mcp 3.88.2 → 3.88.3
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 +8 -0
- package/README.md +1 -1
- package/dist/index.js +1093 -370
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -446,13 +446,13 @@ function setProtectedAccount(locationId2, name, file2 = accountRulesPath()) {
|
|
|
446
446
|
function isWriteMethod(method) {
|
|
447
447
|
return WRITE_METHODS.has(method.toUpperCase());
|
|
448
448
|
}
|
|
449
|
-
function locationIdsInRequest(
|
|
449
|
+
function locationIdsInRequest(path45, opts = {}) {
|
|
450
450
|
const found = /* @__PURE__ */ new Set();
|
|
451
451
|
const add2 = (v) => {
|
|
452
452
|
if (typeof v === "string" && v.trim()) found.add(v.trim());
|
|
453
453
|
else if (Array.isArray(v)) v.forEach(add2);
|
|
454
454
|
};
|
|
455
|
-
const seg = /\/locations\/([^/?#]+)/i.exec(
|
|
455
|
+
const seg = /\/locations\/([^/?#]+)/i.exec(path45);
|
|
456
456
|
if (seg) add2(decodeURIComponent(seg[1]));
|
|
457
457
|
for (const src of [opts.params, opts.body]) {
|
|
458
458
|
if (!src || typeof src !== "object") continue;
|
|
@@ -479,7 +479,7 @@ function protectedRefusalFor(locationId2, rulesFile) {
|
|
|
479
479
|
const name = rules.protected[id];
|
|
480
480
|
return name ? protectedRefusalMessage(name, false) : null;
|
|
481
481
|
}
|
|
482
|
-
function assertWriteAllowed(method,
|
|
482
|
+
function assertWriteAllowed(method, path45, opts = {}) {
|
|
483
483
|
if (!isWriteMethod(method)) return;
|
|
484
484
|
let rules;
|
|
485
485
|
try {
|
|
@@ -488,7 +488,7 @@ function assertWriteAllowed(method, path44, opts = {}) {
|
|
|
488
488
|
throw new Error(UNREADABLE_RULES_MSG);
|
|
489
489
|
}
|
|
490
490
|
if (Object.keys(rules.protected).length === 0) return;
|
|
491
|
-
for (const id of locationIdsInRequest(
|
|
491
|
+
for (const id of locationIdsInRequest(path45, opts)) {
|
|
492
492
|
const name = rules.protected[id];
|
|
493
493
|
if (name) throw new Error(protectedRefusalMessage(name, false));
|
|
494
494
|
}
|
|
@@ -646,8 +646,8 @@ var init_ghl_client = __esm({
|
|
|
646
646
|
Version: version || GHL_API_VERSION
|
|
647
647
|
};
|
|
648
648
|
}
|
|
649
|
-
buildUrl(
|
|
650
|
-
const url = new URL(
|
|
649
|
+
buildUrl(path45, params) {
|
|
650
|
+
const url = new URL(path45, GHL_BASE_URL);
|
|
651
651
|
if (params) {
|
|
652
652
|
for (const [key, value] of Object.entries(params)) {
|
|
653
653
|
if (value !== void 0 && value !== null) {
|
|
@@ -657,19 +657,19 @@ var init_ghl_client = __esm({
|
|
|
657
657
|
}
|
|
658
658
|
return url.toString();
|
|
659
659
|
}
|
|
660
|
-
async request(method,
|
|
660
|
+
async request(method, path45, options = {}, attempt = 0) {
|
|
661
661
|
this.signal?.throwIfAborted();
|
|
662
662
|
if (inBuildRequestScope() && method === "GET" && !inAccountReadAttempt()) {
|
|
663
|
-
return retryAccountRead(() => this.request(method,
|
|
663
|
+
return retryAccountRead(() => this.request(method, path45, options, attempt), `${method} ${path45}`);
|
|
664
664
|
}
|
|
665
665
|
if (attempt === 0) {
|
|
666
|
-
assertWriteAllowed(method,
|
|
666
|
+
assertWriteAllowed(method, path45, {
|
|
667
667
|
params: options.params,
|
|
668
668
|
body: options.body,
|
|
669
669
|
defaultLocationId: this.defaultLocationId
|
|
670
670
|
});
|
|
671
671
|
}
|
|
672
|
-
const url = this.buildUrl(
|
|
672
|
+
const url = this.buildUrl(path45, options.params);
|
|
673
673
|
const headers = this.buildHeaders(options.version);
|
|
674
674
|
const fetchOptions = {
|
|
675
675
|
method,
|
|
@@ -690,14 +690,14 @@ var init_ghl_client = __esm({
|
|
|
690
690
|
clearTimeout(timeout);
|
|
691
691
|
this.signal?.throwIfAborted();
|
|
692
692
|
if (error instanceof Error && error.name === "AbortError") {
|
|
693
|
-
throw requestError(new Error(`Request timeout (30s): ${method} ${
|
|
693
|
+
throw requestError(new Error(`Request timeout (30s): ${method} ${path45}`));
|
|
694
694
|
}
|
|
695
695
|
if (!inBuildRequestScope() && !options.noRetry && attempt < MAX_RETRIES) {
|
|
696
696
|
const delay5 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
|
|
697
|
-
process.stderr.write(`[ghl-mcp] Network error on ${method} ${
|
|
697
|
+
process.stderr.write(`[ghl-mcp] Network error on ${method} ${path45}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
|
|
698
698
|
`);
|
|
699
699
|
await new Promise((r) => setTimeout(r, delay5));
|
|
700
|
-
return this.request(method,
|
|
700
|
+
return this.request(method, path45, options, attempt + 1);
|
|
701
701
|
}
|
|
702
702
|
throw requestError(error);
|
|
703
703
|
} finally {
|
|
@@ -705,10 +705,10 @@ var init_ghl_client = __esm({
|
|
|
705
705
|
}
|
|
706
706
|
if (!inBuildRequestScope() && !options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
|
|
707
707
|
const delay5 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
|
|
708
|
-
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${
|
|
708
|
+
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path45}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
|
|
709
709
|
`);
|
|
710
710
|
await new Promise((r) => setTimeout(r, delay5));
|
|
711
|
-
return this.request(method,
|
|
711
|
+
return this.request(method, path45, options, attempt + 1);
|
|
712
712
|
}
|
|
713
713
|
if (!response.ok) {
|
|
714
714
|
let errorBody = "";
|
|
@@ -717,7 +717,7 @@ var init_ghl_client = __esm({
|
|
|
717
717
|
} catch {
|
|
718
718
|
}
|
|
719
719
|
throw requestError(new Error(
|
|
720
|
-
`GHL API Error ${response.status} ${response.statusText}: ${method} ${
|
|
720
|
+
`GHL API Error ${response.status} ${response.statusText}: ${method} ${path45}
|
|
721
721
|
${errorBody}`
|
|
722
722
|
));
|
|
723
723
|
}
|
|
@@ -738,20 +738,20 @@ ${errorBody}`
|
|
|
738
738
|
combined?.dispose();
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
|
-
async get(
|
|
742
|
-
return this.request("GET",
|
|
741
|
+
async get(path45, options) {
|
|
742
|
+
return this.request("GET", path45, options);
|
|
743
743
|
}
|
|
744
|
-
async post(
|
|
745
|
-
return this.request("POST",
|
|
744
|
+
async post(path45, options) {
|
|
745
|
+
return this.request("POST", path45, options);
|
|
746
746
|
}
|
|
747
|
-
async put(
|
|
748
|
-
return this.request("PUT",
|
|
747
|
+
async put(path45, options) {
|
|
748
|
+
return this.request("PUT", path45, options);
|
|
749
749
|
}
|
|
750
|
-
async patch(
|
|
751
|
-
return this.request("PATCH",
|
|
750
|
+
async patch(path45, options) {
|
|
751
|
+
return this.request("PATCH", path45, options);
|
|
752
752
|
}
|
|
753
|
-
async delete(
|
|
754
|
-
return this.request("DELETE",
|
|
753
|
+
async delete(path45, options) {
|
|
754
|
+
return this.request("DELETE", path45, options);
|
|
755
755
|
}
|
|
756
756
|
/**
|
|
757
757
|
* Helper: resolves locationId from args or falls back to default
|
|
@@ -7109,11 +7109,11 @@ function registerEmailTools(server2, client) {
|
|
|
7109
7109
|
function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
|
|
7110
7110
|
if (!builderClient) return;
|
|
7111
7111
|
const client = builderClient;
|
|
7112
|
-
async function builderRequest2(method,
|
|
7113
|
-
const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${
|
|
7112
|
+
async function builderRequest2(method, path45, body) {
|
|
7113
|
+
const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path45}`, body ? { body } : {});
|
|
7114
7114
|
if (!response.ok) {
|
|
7115
7115
|
const text3 = await response.text();
|
|
7116
|
-
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${
|
|
7116
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path45}
|
|
7117
7117
|
${text3}`);
|
|
7118
7118
|
}
|
|
7119
7119
|
const text2 = await response.text();
|
|
@@ -10913,9 +10913,440 @@ var init_workflow_builder = __esm({
|
|
|
10913
10913
|
}
|
|
10914
10914
|
});
|
|
10915
10915
|
|
|
10916
|
+
// src/command-os/prospect/net-guard.ts
|
|
10917
|
+
function ipv6Bytes(raw) {
|
|
10918
|
+
let s = raw.toLowerCase().replace(/^\[|\]$/g, "").replace(/%.*$/, "");
|
|
10919
|
+
const tail = [];
|
|
10920
|
+
const v4 = s.match(/:(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
10921
|
+
if (v4) {
|
|
10922
|
+
const parts = v4[1].split(".").map(Number);
|
|
10923
|
+
if (parts.some((n) => n > 255)) return void 0;
|
|
10924
|
+
tail.push(...parts);
|
|
10925
|
+
s = s.slice(0, s.length - v4[1].length) + "0:0";
|
|
10926
|
+
}
|
|
10927
|
+
const halves = s.split("::");
|
|
10928
|
+
if (halves.length > 2) return void 0;
|
|
10929
|
+
const groups = (h) => h === "" ? [] : h.split(":");
|
|
10930
|
+
const head4 = groups(halves[0]);
|
|
10931
|
+
const rest = halves.length === 2 ? groups(halves[1]) : [];
|
|
10932
|
+
const fill = halves.length === 2 ? 8 - head4.length - rest.length : 0;
|
|
10933
|
+
if (fill < 0 || halves.length === 1 && head4.length !== 8) return void 0;
|
|
10934
|
+
const all = [...head4, ...Array(fill).fill("0"), ...rest];
|
|
10935
|
+
if (all.length !== 8 || all.some((g) => !/^[0-9a-f]{1,4}$/.test(g))) return void 0;
|
|
10936
|
+
const bytes = all.flatMap((g) => {
|
|
10937
|
+
const n = parseInt(g, 16);
|
|
10938
|
+
return [n >> 8, n & 255];
|
|
10939
|
+
});
|
|
10940
|
+
if (tail.length) bytes.splice(12, 4, ...tail);
|
|
10941
|
+
return bytes;
|
|
10942
|
+
}
|
|
10943
|
+
function privateV4(b) {
|
|
10944
|
+
const [a, c, d] = b;
|
|
10945
|
+
return a === 0 || a === 10 || a === 100 && c >= 64 && c <= 127 || a === 127 || a === 169 && c === 254 || a === 172 && c >= 16 && c <= 31 || a === 192 && c === 0 && d === 0 || a === 192 && c === 0 && d === 2 || a === 192 && c === 88 && d === 99 || a === 192 && c === 168 || a === 198 && (c === 18 || c === 19) || a === 198 && c === 51 && d === 100 || a === 203 && c === 0 && d === 113 || a >= 224;
|
|
10946
|
+
}
|
|
10947
|
+
function isPrivateAddress(ip) {
|
|
10948
|
+
const bare = ip.replace(/^\[|\]$/g, "").replace(/%.*$/, "");
|
|
10949
|
+
const v = import_node_net.default.isIP(bare);
|
|
10950
|
+
if (v === 4) return privateV4(bare.split(".").map(Number));
|
|
10951
|
+
if (v !== 6) return true;
|
|
10952
|
+
const b = ipv6Bytes(bare);
|
|
10953
|
+
if (!b) return true;
|
|
10954
|
+
const zeroTo = (n) => b.slice(0, n).every((x) => x === 0);
|
|
10955
|
+
if (zeroTo(16)) return true;
|
|
10956
|
+
if (zeroTo(15) && b[15] === 1) return true;
|
|
10957
|
+
if (zeroTo(10) && b[10] === 255 && b[11] === 255) return privateV4(b.slice(12));
|
|
10958
|
+
if (zeroTo(12)) return true;
|
|
10959
|
+
if (zeroTo(8) && b[8] === 255 && b[9] === 255 && b[10] === 0 && b[11] === 0) return true;
|
|
10960
|
+
if (b[0] === 32 && b[1] === 2) return privateV4(b.slice(2, 6));
|
|
10961
|
+
if ((b[0] & 224) !== 32) return true;
|
|
10962
|
+
if (b[0] === 32 && b[1] === 1 && (b[2] & 254) === 0) return true;
|
|
10963
|
+
if (b[0] === 32 && b[1] === 1 && b[2] === 13 && b[3] === 184) return true;
|
|
10964
|
+
if (b[0] === 63 && b[1] === 255 && (b[2] & 240) === 0) return true;
|
|
10965
|
+
return false;
|
|
10966
|
+
}
|
|
10967
|
+
function withDeadline(p, ms, why, signal) {
|
|
10968
|
+
return new Promise((resolve13, reject) => {
|
|
10969
|
+
if (signal?.aborted) {
|
|
10970
|
+
p.catch(() => void 0);
|
|
10971
|
+
reject(new Error("aborted"));
|
|
10972
|
+
return;
|
|
10973
|
+
}
|
|
10974
|
+
const t = setTimeout(() => reject(new Error(why)), ms);
|
|
10975
|
+
const onAbort = () => {
|
|
10976
|
+
clearTimeout(t);
|
|
10977
|
+
reject(new Error("aborted"));
|
|
10978
|
+
};
|
|
10979
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
10980
|
+
p.then((v) => {
|
|
10981
|
+
clearTimeout(t);
|
|
10982
|
+
signal?.removeEventListener("abort", onAbort);
|
|
10983
|
+
resolve13(v);
|
|
10984
|
+
}, (e) => {
|
|
10985
|
+
clearTimeout(t);
|
|
10986
|
+
signal?.removeEventListener("abort", onAbort);
|
|
10987
|
+
reject(e);
|
|
10988
|
+
});
|
|
10989
|
+
});
|
|
10990
|
+
}
|
|
10991
|
+
async function publicAddressProblem(raw, lookup = import_node_dns.default.promises.lookup) {
|
|
10992
|
+
let u;
|
|
10993
|
+
try {
|
|
10994
|
+
u = new URL(raw);
|
|
10995
|
+
} catch {
|
|
10996
|
+
return "not a web address";
|
|
10997
|
+
}
|
|
10998
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return `only web addresses are opened (this one is ${u.protocol})`;
|
|
10999
|
+
if (u.username || u.password) return "an address carrying a login is not opened";
|
|
11000
|
+
const host = u.hostname.replace(/^\[|\]$/g, "");
|
|
11001
|
+
if (LOCAL_NAME.test(host)) return "a local or private address is never opened";
|
|
11002
|
+
if (import_node_net.default.isIP(host)) return isPrivateAddress(host) ? "a local or private address is never opened" : void 0;
|
|
11003
|
+
try {
|
|
11004
|
+
const all = await withDeadline(lookup(host, { all: true, verbatim: true }), 8e3, "the address lookup took too long");
|
|
11005
|
+
if (!all.length) return "the address does not resolve";
|
|
11006
|
+
if (all.some((a) => isPrivateAddress(a.address))) return "the address resolves to a local or private network, so it is never opened";
|
|
11007
|
+
} catch {
|
|
11008
|
+
return "the address does not resolve";
|
|
11009
|
+
}
|
|
11010
|
+
return void 0;
|
|
11011
|
+
}
|
|
11012
|
+
function publicOnlyLookup(hostname2, options, callback) {
|
|
11013
|
+
import_node_dns.default.lookup(hostname2, { ...options, all: true, verbatim: true }, (err, addresses) => {
|
|
11014
|
+
if (err) return callback(err, "", 0);
|
|
11015
|
+
const list3 = addresses;
|
|
11016
|
+
const bad = list3.find((a) => isPrivateAddress(a.address));
|
|
11017
|
+
if (!list3.length || bad) {
|
|
11018
|
+
const e = new Error("the address resolves to a local or private network, so it is never opened");
|
|
11019
|
+
e.code = "EPRIVATE";
|
|
11020
|
+
return callback(e, "", 0);
|
|
11021
|
+
}
|
|
11022
|
+
if (options.all) return callback(null, list3);
|
|
11023
|
+
return callback(null, list3[0].address, list3[0].family);
|
|
11024
|
+
});
|
|
11025
|
+
}
|
|
11026
|
+
function transferBudget(totalBytes = 25e7) {
|
|
11027
|
+
const opts = { keepAlive: true, maxSockets: 8, lookup: publicOnlyLookup };
|
|
11028
|
+
return { bytesLeft: totalBytes, agents: { http: new import_node_http.default.Agent(opts), https: new import_node_https.default.Agent(opts) } };
|
|
11029
|
+
}
|
|
11030
|
+
function closeTransferBudget(b) {
|
|
11031
|
+
b.agents.http.destroy();
|
|
11032
|
+
b.agents.https.destroy();
|
|
11033
|
+
}
|
|
11034
|
+
function guardedTransfer(url, init) {
|
|
11035
|
+
return new Promise((resolve13, reject) => {
|
|
11036
|
+
let u;
|
|
11037
|
+
try {
|
|
11038
|
+
u = new URL(url);
|
|
11039
|
+
} catch {
|
|
11040
|
+
reject(new Error("not a web address"));
|
|
11041
|
+
return;
|
|
11042
|
+
}
|
|
11043
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
11044
|
+
reject(new Error("only web addresses are opened"));
|
|
11045
|
+
return;
|
|
11046
|
+
}
|
|
11047
|
+
if (u.username || u.password) {
|
|
11048
|
+
reject(new Error("an address carrying a login is not opened"));
|
|
11049
|
+
return;
|
|
11050
|
+
}
|
|
11051
|
+
if (refusedHost(u.hostname)) {
|
|
11052
|
+
reject(new Error("a local or private address is never opened"));
|
|
11053
|
+
return;
|
|
11054
|
+
}
|
|
11055
|
+
let settled = false;
|
|
11056
|
+
let cleanup = () => void 0;
|
|
11057
|
+
const settle2 = (f) => {
|
|
11058
|
+
if (settled) return;
|
|
11059
|
+
settled = true;
|
|
11060
|
+
init.signal.removeEventListener("abort", onAbort);
|
|
11061
|
+
cleanup();
|
|
11062
|
+
f();
|
|
11063
|
+
};
|
|
11064
|
+
const onAbort = () => settle2(() => {
|
|
11065
|
+
req.destroy();
|
|
11066
|
+
reject(new Error("aborted"));
|
|
11067
|
+
});
|
|
11068
|
+
if (init.signal.aborted) {
|
|
11069
|
+
reject(new Error("aborted"));
|
|
11070
|
+
return;
|
|
11071
|
+
}
|
|
11072
|
+
init.signal.addEventListener("abort", onAbort, { once: true });
|
|
11073
|
+
const isHttps = u.protocol === "https:";
|
|
11074
|
+
const headers = {};
|
|
11075
|
+
for (const [k, v] of Object.entries(init.headers)) if (!/^(?:host|connection|keep-alive|proxy-.*|transfer-encoding|upgrade|te|trailer|content-length|:.*)$/i.test(k)) headers[k] = v;
|
|
11076
|
+
headers["accept-encoding"] = "gzip, deflate, br";
|
|
11077
|
+
const req = (isHttps ? import_node_https.default : import_node_http.default).request(u, { method: init.method, headers, agent: isHttps ? init.budget.agents.https : init.budget.agents.http, lookup: publicOnlyLookup }, (res) => {
|
|
11078
|
+
const out = [];
|
|
11079
|
+
for (let i = 0; i < res.rawHeaders.length; i += 2) out.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
|
|
11080
|
+
const enc = (res.headers["content-encoding"] ?? "").toString().toLowerCase();
|
|
11081
|
+
const unzip = enc.includes("br") ? import_node_zlib.default.createBrotliDecompress() : enc.includes("gzip") ? import_node_zlib.default.createGunzip() : enc.includes("deflate") ? import_node_zlib.default.createInflate() : void 0;
|
|
11082
|
+
const stream = unzip ? res.pipe(unzip) : res;
|
|
11083
|
+
cleanup = () => {
|
|
11084
|
+
unzip?.destroy();
|
|
11085
|
+
res.destroy();
|
|
11086
|
+
};
|
|
11087
|
+
const kept = unzip ? out.filter(([k]) => !/^(?:content-encoding|content-length)$/i.test(k)) : out;
|
|
11088
|
+
const chunks = [];
|
|
11089
|
+
let size = 0;
|
|
11090
|
+
const finish3 = (incomplete) => settle2(() => resolve13({ status: res.statusCode ?? 0, headers: kept, body: Buffer.concat(chunks), incomplete }));
|
|
11091
|
+
stream.on("data", (c) => {
|
|
11092
|
+
size += c.length;
|
|
11093
|
+
init.budget.bytesLeft -= c.length;
|
|
11094
|
+
if (size > init.maxBytes) finish3(`the file is larger than ${Math.round(init.maxBytes / 1e6)} MB, so it was not loaded`);
|
|
11095
|
+
else if (init.budget.bytesLeft < 0) finish3("the run's limit on how much it downloads was reached");
|
|
11096
|
+
else chunks.push(c);
|
|
11097
|
+
});
|
|
11098
|
+
stream.on("end", () => finish3(res.complete ? void 0 : "the file did not arrive whole"));
|
|
11099
|
+
stream.on("error", () => finish3("the file did not arrive whole"));
|
|
11100
|
+
res.on("aborted", () => finish3("the file did not arrive whole"));
|
|
11101
|
+
res.on("error", () => finish3("the file did not arrive whole"));
|
|
11102
|
+
res.on("close", () => {
|
|
11103
|
+
if (!res.complete) finish3("the file did not arrive whole");
|
|
11104
|
+
else if (!unzip) finish3();
|
|
11105
|
+
});
|
|
11106
|
+
if (unzip) unzip.on("close", () => finish3(res.complete ? void 0 : "the file did not arrive whole"));
|
|
11107
|
+
});
|
|
11108
|
+
req.setTimeout(3e4, () => req.destroy(new Error("no answer within 30 seconds")));
|
|
11109
|
+
req.on("error", (e) => settle2(() => reject(e)));
|
|
11110
|
+
req.end();
|
|
11111
|
+
});
|
|
11112
|
+
}
|
|
11113
|
+
async function guardedFetchLike(url, init = {}) {
|
|
11114
|
+
const budget = transferBudget(6e7);
|
|
11115
|
+
try {
|
|
11116
|
+
let cur = url;
|
|
11117
|
+
const signal = init.signal ?? AbortSignal.timeout(2e4);
|
|
11118
|
+
for (let hop = 0; hop <= 5; hop++) {
|
|
11119
|
+
if (signal.aborted) throw new Error("aborted");
|
|
11120
|
+
const problem = await withDeadline(publicAddressProblem(cur), 9e3, "the address lookup took too long", signal);
|
|
11121
|
+
if (problem && problem !== "the address does not resolve") throw new Error(problem);
|
|
11122
|
+
const r = await guardedTransfer(cur, { method: "GET", headers: init.headers ?? {}, signal, maxBytes: init.statusOnly ? 65536 : Math.min(init.maxBytes ?? 15e6, 6e7), budget });
|
|
11123
|
+
const cutBySize = init.statusOnly && r.incomplete?.startsWith("the file is larger than");
|
|
11124
|
+
if (r.incomplete && !cutBySize) throw new Error(r.incomplete);
|
|
11125
|
+
const loc = r.headers.find(([k]) => k.toLowerCase() === "location")?.[1];
|
|
11126
|
+
if (r.status >= 300 && r.status < 400 && loc && init.redirect !== "manual") {
|
|
11127
|
+
cur = new URL(loc, cur).toString();
|
|
11128
|
+
continue;
|
|
11129
|
+
}
|
|
11130
|
+
const body = r.body;
|
|
11131
|
+
const get = (n) => r.headers.find(([k]) => k.toLowerCase() === n.toLowerCase())?.[1] ?? null;
|
|
11132
|
+
return { ok: r.status >= 200 && r.status < 300, status: r.status, url: cur, text: async () => body.toString("utf8"), arrayBuffer: async () => body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength), headers: { get } };
|
|
11133
|
+
}
|
|
11134
|
+
throw new Error("more than 5 redirects");
|
|
11135
|
+
} finally {
|
|
11136
|
+
closeTransferBudget(budget);
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
async function guardedPost(url, body, headers, timeoutMs = 2e4) {
|
|
11140
|
+
const started = Date.now();
|
|
11141
|
+
const problem = await withDeadline(publicAddressProblem(url), timeoutMs, `no answer within ${Math.round(timeoutMs / 1e3)} seconds`);
|
|
11142
|
+
if (problem && problem !== "the address does not resolve") throw new Error(problem);
|
|
11143
|
+
const left = Math.max(1, timeoutMs - (Date.now() - started));
|
|
11144
|
+
const u = new URL(url);
|
|
11145
|
+
const mod = u.protocol === "https:" ? import_node_https.default : import_node_http.default;
|
|
11146
|
+
return new Promise((resolve13, reject) => {
|
|
11147
|
+
let settled = false;
|
|
11148
|
+
const chunks = [];
|
|
11149
|
+
let size = 0;
|
|
11150
|
+
const settle2 = (f) => {
|
|
11151
|
+
if (settled) return;
|
|
11152
|
+
settled = true;
|
|
11153
|
+
clearTimeout(timer);
|
|
11154
|
+
f();
|
|
11155
|
+
};
|
|
11156
|
+
const req = mod.request(u, { method: "POST", headers: { ...headers, "content-length": String(Buffer.byteLength(body)) }, lookup: publicOnlyLookup }, (res) => {
|
|
11157
|
+
const done = () => settle2(() => resolve13({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString("utf8") }));
|
|
11158
|
+
res.on("data", (c) => {
|
|
11159
|
+
size += c.length;
|
|
11160
|
+
if (size <= 1e6) chunks.push(c);
|
|
11161
|
+
else {
|
|
11162
|
+
res.destroy();
|
|
11163
|
+
done();
|
|
11164
|
+
}
|
|
11165
|
+
});
|
|
11166
|
+
res.on("end", done);
|
|
11167
|
+
res.on("error", done);
|
|
11168
|
+
res.on("close", done);
|
|
11169
|
+
});
|
|
11170
|
+
const timer = setTimeout(() => settle2(() => {
|
|
11171
|
+
req.destroy();
|
|
11172
|
+
reject(new Error(`no answer within ${Math.round(timeoutMs / 1e3)} seconds`));
|
|
11173
|
+
}), left);
|
|
11174
|
+
req.on("error", (e) => settle2(() => reject(e)));
|
|
11175
|
+
req.end(body);
|
|
11176
|
+
});
|
|
11177
|
+
}
|
|
11178
|
+
function refusedHost(host) {
|
|
11179
|
+
const bare = host.replace(/^\[|\]$/g, "");
|
|
11180
|
+
if (!bare || LOCAL_NAME.test(bare)) return true;
|
|
11181
|
+
if (import_node_net.default.isIP(bare.replace(/%.*$/, ""))) return isPrivateAddress(bare);
|
|
11182
|
+
return /^(?:0x[0-9a-f]*|\d+)(?:\.(?:0x[0-9a-f]*|\d+)){0,3}\.?$/i.test(bare) || bare.includes(":");
|
|
11183
|
+
}
|
|
11184
|
+
async function startGuardProxy(opts = {}) {
|
|
11185
|
+
const max = opts.maxConnections ?? 8e3;
|
|
11186
|
+
const maxOpen = opts.maxOpen ?? 256;
|
|
11187
|
+
const idleMs = opts.idleMs ?? 3e4;
|
|
11188
|
+
const username = "shopper";
|
|
11189
|
+
const password = (0, import_node_crypto3.randomBytes)(18).toString("hex");
|
|
11190
|
+
const expected = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
11191
|
+
const stats = { connections: 0, refused: 0, open: 0 };
|
|
11192
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
11193
|
+
const track = (s) => {
|
|
11194
|
+
sockets.add(s);
|
|
11195
|
+
s.once("close", () => sockets.delete(s));
|
|
11196
|
+
};
|
|
11197
|
+
const authorised = (h) => {
|
|
11198
|
+
const got = Buffer.from(String(h ?? ""));
|
|
11199
|
+
const want = Buffer.from(expected);
|
|
11200
|
+
return got.length === want.length && (0, import_node_crypto3.timingSafeEqual)(got, want);
|
|
11201
|
+
};
|
|
11202
|
+
const server2 = import_node_http.default.createServer((req, res) => {
|
|
11203
|
+
if (!authorised(req.headers["proxy-authorization"])) {
|
|
11204
|
+
stats.refused++;
|
|
11205
|
+
res.writeHead(407, { "proxy-authenticate": 'Basic realm="shopper"' }).end();
|
|
11206
|
+
return;
|
|
11207
|
+
}
|
|
11208
|
+
let u;
|
|
11209
|
+
try {
|
|
11210
|
+
u = new URL(req.url ?? "");
|
|
11211
|
+
} catch {
|
|
11212
|
+
res.writeHead(400).end();
|
|
11213
|
+
return;
|
|
11214
|
+
}
|
|
11215
|
+
if (u.protocol !== "http:" || !["GET", "HEAD"].includes(req.method ?? "") || req.headers.upgrade) {
|
|
11216
|
+
stats.refused++;
|
|
11217
|
+
res.writeHead(405).end();
|
|
11218
|
+
return;
|
|
11219
|
+
}
|
|
11220
|
+
if (++stats.connections > max) {
|
|
11221
|
+
stats.refused++;
|
|
11222
|
+
res.writeHead(429).end();
|
|
11223
|
+
return;
|
|
11224
|
+
}
|
|
11225
|
+
const port2 = Number(u.port || 80);
|
|
11226
|
+
if (!WEB_PORTS.has(port2) || refusedHost(u.hostname)) {
|
|
11227
|
+
stats.refused++;
|
|
11228
|
+
res.writeHead(403).end();
|
|
11229
|
+
return;
|
|
11230
|
+
}
|
|
11231
|
+
const headers = { ...req.headers };
|
|
11232
|
+
delete headers["proxy-connection"];
|
|
11233
|
+
delete headers["proxy-authorization"];
|
|
11234
|
+
const out = import_node_http.default.request({ host: u.hostname.replace(/^\[|\]$/g, ""), port: port2, path: u.pathname + u.search, method: req.method, headers, lookup: publicOnlyLookup, timeout: idleMs }, (up) => {
|
|
11235
|
+
res.writeHead(up.statusCode ?? 502, up.headers);
|
|
11236
|
+
up.pipe(res);
|
|
11237
|
+
});
|
|
11238
|
+
out.on("socket", (s) => track(s));
|
|
11239
|
+
out.on("error", () => {
|
|
11240
|
+
stats.refused++;
|
|
11241
|
+
if (!res.headersSent) res.writeHead(502);
|
|
11242
|
+
res.end();
|
|
11243
|
+
});
|
|
11244
|
+
out.on("timeout", () => out.destroy());
|
|
11245
|
+
out.end();
|
|
11246
|
+
});
|
|
11247
|
+
server2.on("connection", (s) => {
|
|
11248
|
+
s.on("error", () => void 0);
|
|
11249
|
+
if (sockets.size >= maxOpen) {
|
|
11250
|
+
stats.refused++;
|
|
11251
|
+
s.destroy();
|
|
11252
|
+
return;
|
|
11253
|
+
}
|
|
11254
|
+
track(s);
|
|
11255
|
+
stats.open = Math.max(stats.open, sockets.size);
|
|
11256
|
+
s.setTimeout(idleMs, () => s.destroy());
|
|
11257
|
+
});
|
|
11258
|
+
server2.headersTimeout = Math.min(idleMs, 1e4);
|
|
11259
|
+
server2.requestTimeout = idleMs;
|
|
11260
|
+
server2.on("connect", (req, client, head4) => {
|
|
11261
|
+
client.on("error", () => void 0);
|
|
11262
|
+
if (!authorised(req.headers["proxy-authorization"])) {
|
|
11263
|
+
stats.refused++;
|
|
11264
|
+
client.end('HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="shopper"\r\nContent-Length: 0\r\n\r\n');
|
|
11265
|
+
return;
|
|
11266
|
+
}
|
|
11267
|
+
const [host, portStr] = (req.url ?? "").split(/:(?=\d+$)/);
|
|
11268
|
+
const port2 = Number(portStr || 443);
|
|
11269
|
+
if (++stats.connections > max || !WEB_PORTS.has(port2) || refusedHost(host ?? "")) {
|
|
11270
|
+
stats.refused++;
|
|
11271
|
+
client.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
11272
|
+
return;
|
|
11273
|
+
}
|
|
11274
|
+
const up = import_node_net.default.connect({ host: (host ?? "").replace(/^\[|\]$/g, ""), port: port2, lookup: publicOnlyLookup, timeout: idleMs }, () => {
|
|
11275
|
+
client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
11276
|
+
if (head4.length) up.write(head4);
|
|
11277
|
+
up.pipe(client);
|
|
11278
|
+
client.pipe(up);
|
|
11279
|
+
});
|
|
11280
|
+
track(up);
|
|
11281
|
+
up.on("error", () => {
|
|
11282
|
+
stats.refused++;
|
|
11283
|
+
client.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
|
11284
|
+
});
|
|
11285
|
+
up.on("timeout", () => {
|
|
11286
|
+
up.destroy();
|
|
11287
|
+
client.destroy();
|
|
11288
|
+
});
|
|
11289
|
+
client.on("error", () => up.destroy());
|
|
11290
|
+
client.on("close", () => up.destroy());
|
|
11291
|
+
});
|
|
11292
|
+
server2.on("clientError", (_e, s) => s.destroy());
|
|
11293
|
+
await new Promise((resolve13, reject) => {
|
|
11294
|
+
const fail = (e) => {
|
|
11295
|
+
server2.removeListener("listening", ok);
|
|
11296
|
+
reject(e);
|
|
11297
|
+
};
|
|
11298
|
+
const ok = () => {
|
|
11299
|
+
server2.removeListener("error", fail);
|
|
11300
|
+
resolve13();
|
|
11301
|
+
};
|
|
11302
|
+
server2.once("error", fail);
|
|
11303
|
+
server2.once("listening", ok);
|
|
11304
|
+
try {
|
|
11305
|
+
server2.listen(0, "127.0.0.1");
|
|
11306
|
+
} catch (e) {
|
|
11307
|
+
server2.removeListener("error", fail);
|
|
11308
|
+
server2.removeListener("listening", ok);
|
|
11309
|
+
reject(e);
|
|
11310
|
+
}
|
|
11311
|
+
});
|
|
11312
|
+
server2.on("error", () => void 0);
|
|
11313
|
+
const port = server2.address().port;
|
|
11314
|
+
return {
|
|
11315
|
+
url: `http://127.0.0.1:${port}`,
|
|
11316
|
+
username,
|
|
11317
|
+
password,
|
|
11318
|
+
stats,
|
|
11319
|
+
// Every socket is destroyed and the close never waits more than two seconds.
|
|
11320
|
+
close: () => new Promise((r) => {
|
|
11321
|
+
const t = setTimeout(r, 2e3);
|
|
11322
|
+
server2.close(() => {
|
|
11323
|
+
clearTimeout(t);
|
|
11324
|
+
r();
|
|
11325
|
+
});
|
|
11326
|
+
for (const s of sockets) s.destroy();
|
|
11327
|
+
server2.closeAllConnections?.();
|
|
11328
|
+
})
|
|
11329
|
+
};
|
|
11330
|
+
}
|
|
11331
|
+
var import_node_dns, import_node_http, import_node_https, import_node_net, import_node_zlib, import_node_crypto3, LOCAL_NAME, WEB_PORTS;
|
|
11332
|
+
var init_net_guard = __esm({
|
|
11333
|
+
"src/command-os/prospect/net-guard.ts"() {
|
|
11334
|
+
"use strict";
|
|
11335
|
+
import_node_dns = __toESM(require("node:dns"));
|
|
11336
|
+
import_node_http = __toESM(require("node:http"));
|
|
11337
|
+
import_node_https = __toESM(require("node:https"));
|
|
11338
|
+
import_node_net = __toESM(require("node:net"));
|
|
11339
|
+
import_node_zlib = __toESM(require("node:zlib"));
|
|
11340
|
+
import_node_crypto3 = require("node:crypto");
|
|
11341
|
+
LOCAL_NAME = /^(?:localhost|localhost\.localdomain|ip6-localhost|ip6-loopback)\.?$|\.(?:localhost|local|internal|lan|home|corp|intranet|localdomain|home\.arpa)\.?$/i;
|
|
11342
|
+
WEB_PORTS = /* @__PURE__ */ new Set([80, 443, 8080, 8443]);
|
|
11343
|
+
}
|
|
11344
|
+
});
|
|
11345
|
+
|
|
10916
11346
|
// src/page-reads.ts
|
|
10917
11347
|
var page_reads_exports = {};
|
|
10918
11348
|
__export(page_reads_exports, {
|
|
11349
|
+
pageReadsNet: () => pageReadsNet,
|
|
10919
11350
|
readPageContent: () => readPageContent,
|
|
10920
11351
|
readPageFull: () => readPageFull
|
|
10921
11352
|
});
|
|
@@ -10923,11 +11354,11 @@ async function readPageFull(client, pageId, fetcher = fetch) {
|
|
|
10923
11354
|
const headers = await client.buildHeaders();
|
|
10924
11355
|
headers.Origin = "https://app.gohighlevel.com";
|
|
10925
11356
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
10926
|
-
const
|
|
10927
|
-
const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${
|
|
11357
|
+
const path45 = `/page/${pageId}?locationId=${client.locationId}`;
|
|
11358
|
+
const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path45}`, { method: "GET", headers });
|
|
10928
11359
|
if (!response.ok) {
|
|
10929
11360
|
const text3 = await response.text();
|
|
10930
|
-
throw new Error(`Funnel API Error ${response.status}: GET ${
|
|
11361
|
+
throw new Error(`Funnel API Error ${response.status}: GET ${path45}
|
|
10931
11362
|
${text3}`);
|
|
10932
11363
|
}
|
|
10933
11364
|
const text2 = await response.text();
|
|
@@ -10938,20 +11369,24 @@ ${text3}`);
|
|
|
10938
11369
|
return JSON.parse(text2.replace(/[\x00-\x1F\x7F]/g, ""));
|
|
10939
11370
|
}
|
|
10940
11371
|
}
|
|
10941
|
-
async function readPageContent(pageDataDownloadUrl, fetcher =
|
|
11372
|
+
async function readPageContent(pageDataDownloadUrl, fetcher = (url) => pageReadsNet.read(url)) {
|
|
10942
11373
|
const parsedUrl = new URL(pageDataDownloadUrl);
|
|
10943
11374
|
const allowedHosts = ["firebasestorage.googleapis.com", "storage.googleapis.com", "backend.leadconnectorhq.com"];
|
|
10944
|
-
if (!allowedHosts.some((h) => parsedUrl.hostname === h || parsedUrl.hostname.endsWith("." + h))) {
|
|
10945
|
-
throw new Error("URL must be from GHL storage (firebasestorage.googleapis.com). Got: " + parsedUrl.hostname);
|
|
11375
|
+
if (parsedUrl.protocol !== "https:" || !allowedHosts.some((h) => parsedUrl.hostname === h || parsedUrl.hostname.endsWith("." + h))) {
|
|
11376
|
+
throw new Error("URL must be an https address from GHL storage (firebasestorage.googleapis.com). Got: " + parsedUrl.protocol + "//" + parsedUrl.hostname);
|
|
10946
11377
|
}
|
|
10947
11378
|
const response = await fetcher(pageDataDownloadUrl);
|
|
10948
11379
|
if (!response.ok) throw new Error(`Failed to download page data: ${response.status}`);
|
|
10949
|
-
|
|
10950
|
-
return data;
|
|
11380
|
+
return JSON.parse(await response.text());
|
|
10951
11381
|
}
|
|
11382
|
+
var pageReadsNet;
|
|
10952
11383
|
var init_page_reads = __esm({
|
|
10953
11384
|
"src/page-reads.ts"() {
|
|
10954
11385
|
"use strict";
|
|
11386
|
+
init_net_guard();
|
|
11387
|
+
pageReadsNet = {
|
|
11388
|
+
read: (url) => guardedFetchLike(url, { signal: AbortSignal.timeout(3e4), maxBytes: 5e7 })
|
|
11389
|
+
};
|
|
10955
11390
|
}
|
|
10956
11391
|
});
|
|
10957
11392
|
|
|
@@ -10986,20 +11421,20 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
10986
11421
|
return;
|
|
10987
11422
|
}
|
|
10988
11423
|
const client = builderClient;
|
|
10989
|
-
async function internalGet(
|
|
10990
|
-
return client.request("GET",
|
|
11424
|
+
async function internalGet(path45) {
|
|
11425
|
+
return client.request("GET", path45);
|
|
10991
11426
|
}
|
|
10992
|
-
async function internalPost(
|
|
10993
|
-
return client.request("POST",
|
|
11427
|
+
async function internalPost(path45, body) {
|
|
11428
|
+
return client.request("POST", path45, body);
|
|
10994
11429
|
}
|
|
10995
|
-
async function internalPut(
|
|
10996
|
-
return client.request("PUT",
|
|
11430
|
+
async function internalPut(path45, body) {
|
|
11431
|
+
return client.request("PUT", path45, body);
|
|
10997
11432
|
}
|
|
10998
|
-
async function internalDelete(
|
|
10999
|
-
return client.request("DELETE",
|
|
11433
|
+
async function internalDelete(path45) {
|
|
11434
|
+
return client.request("DELETE", path45);
|
|
11000
11435
|
}
|
|
11001
|
-
async function funnelRequest(method,
|
|
11002
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
11436
|
+
async function funnelRequest(method, path45, body) {
|
|
11437
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path45}`;
|
|
11003
11438
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
11004
11439
|
const response = await builderRequest(client, method, url, {
|
|
11005
11440
|
...sendsBody ? { body } : {},
|
|
@@ -11012,7 +11447,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
11012
11447
|
});
|
|
11013
11448
|
if (!response.ok) {
|
|
11014
11449
|
const text3 = await response.text();
|
|
11015
|
-
throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
11450
|
+
throw new Error(`Funnel API Error ${response.status}: ${method} ${path45}
|
|
11016
11451
|
${text3}`);
|
|
11017
11452
|
}
|
|
11018
11453
|
const text2 = await response.text();
|
|
@@ -11603,17 +12038,17 @@ function requireCompany(api) {
|
|
|
11603
12038
|
}
|
|
11604
12039
|
return company;
|
|
11605
12040
|
}
|
|
11606
|
-
async function call(api,
|
|
12041
|
+
async function call(api, path45, init) {
|
|
11607
12042
|
const headers = { ...await api.headers(), Origin: "https://app.gohighlevel.com", Referer: "https://app.gohighlevel.com/" };
|
|
11608
|
-
if (MUTATING_PATHS.some((mutating) =>
|
|
11609
|
-
assertWriteAllowed("POST", SVC +
|
|
12043
|
+
if (MUTATING_PATHS.some((mutating) => path45.startsWith(mutating))) {
|
|
12044
|
+
assertWriteAllowed("POST", SVC + path45, { defaultLocationId: api.locationId() });
|
|
11610
12045
|
}
|
|
11611
12046
|
const doFetch = api.fetch ?? fetch;
|
|
11612
|
-
const response = await doFetch(SVC +
|
|
12047
|
+
const response = await doFetch(SVC + path45, { ...init, headers });
|
|
11613
12048
|
const text2 = await response.text();
|
|
11614
12049
|
if (!response.ok) {
|
|
11615
12050
|
const hint = response.status === 401 && /not authorized to access this company/i.test(text2) ? " \u2014 the Firebase session is authenticated as a DIFFERENT company than the one that owns this sub-account. Run switch_location for this sub-account and try again." : response.status === 401 ? " \u2014 the template library needs the workflow-builder Firebase session, not the API key alone. Run enable_workflow_builder for this sub-account." : "";
|
|
11616
|
-
throw new Error(`GoHighLevel template library ${response.status} on ${
|
|
12051
|
+
throw new Error(`GoHighLevel template library ${response.status} on ${path45}${hint}
|
|
11617
12052
|
${text2.slice(0, 300)}`);
|
|
11618
12053
|
}
|
|
11619
12054
|
return text2 ? JSON.parse(text2) : {};
|
|
@@ -11792,7 +12227,7 @@ async function confirmPreviewUrl(pageId, timeoutMs = 2e4) {
|
|
|
11792
12227
|
const controller = new AbortController();
|
|
11793
12228
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11794
12229
|
try {
|
|
11795
|
-
const response = await
|
|
12230
|
+
const response = await guardedFetchLike(url, { signal: controller.signal, headers: { Accept: "text/html,*/*" } });
|
|
11796
12231
|
const body = await response.text();
|
|
11797
12232
|
const bytes = body.length;
|
|
11798
12233
|
const reachable = response.ok && bytes > 0;
|
|
@@ -11896,6 +12331,7 @@ var init_ghl_page = __esm({
|
|
|
11896
12331
|
"src/command-os/design/ghl-page.ts"() {
|
|
11897
12332
|
"use strict";
|
|
11898
12333
|
init_theme();
|
|
12334
|
+
init_net_guard();
|
|
11899
12335
|
GHL_PREVIEW_HOST = "https://sites.leadconnectorhq.com";
|
|
11900
12336
|
SLOT_LABEL = {
|
|
11901
12337
|
primary: { id: "E1", label: "brand colour" },
|
|
@@ -11970,12 +12406,12 @@ function normaliseColour(value) {
|
|
|
11970
12406
|
function renderedPalette(envelope) {
|
|
11971
12407
|
const defs = pageVariables(envelope);
|
|
11972
12408
|
const paints = [];
|
|
11973
|
-
const walk = (node,
|
|
11974
|
-
if (Array.isArray(node)) return void node.forEach((x, i) => walk(x, `${
|
|
12409
|
+
const walk = (node, path45, prop) => {
|
|
12410
|
+
if (Array.isArray(node)) return void node.forEach((x, i) => walk(x, `${path45}[${i}]`, prop));
|
|
11975
12411
|
if (isRecord8(node)) {
|
|
11976
12412
|
for (const [k, x] of Object.entries(node)) {
|
|
11977
12413
|
if (DEFINITION_KEYS.has(k) || k.startsWith("--")) continue;
|
|
11978
|
-
walk(x, `${
|
|
12414
|
+
walk(x, `${path45}.${k}`, WRAPPER_KEYS.has(k) ? prop : k);
|
|
11979
12415
|
}
|
|
11980
12416
|
return;
|
|
11981
12417
|
}
|
|
@@ -11985,7 +12421,7 @@ function renderedPalette(envelope) {
|
|
|
11985
12421
|
const resolved2 = resolveValue(raw, defs);
|
|
11986
12422
|
const colour = normaliseColour(resolved2);
|
|
11987
12423
|
if (colour === null && resolved2 !== null) return;
|
|
11988
|
-
paints.push({ path:
|
|
12424
|
+
paints.push({ path: path45, prop, kind: kindOf(prop), colour });
|
|
11989
12425
|
};
|
|
11990
12426
|
walk(envelope, "", "");
|
|
11991
12427
|
const face = (name, settingsKey) => {
|
|
@@ -12080,7 +12516,7 @@ var init_rendered_palette = __esm({
|
|
|
12080
12516
|
VISIBLE_CHANGE = 0.05;
|
|
12081
12517
|
BARELY_SHARE = 0.5;
|
|
12082
12518
|
CARRIES_COLOUR = 0.02;
|
|
12083
|
-
paintGroup = (
|
|
12519
|
+
paintGroup = (path45) => /^\.(sections|popups)\[\d+\]/.exec(path45)?.[0]?.slice(1) ?? "page";
|
|
12084
12520
|
looksDifferent = (a, b) => {
|
|
12085
12521
|
if (a === b) return false;
|
|
12086
12522
|
if (a === "transparent" || b === "transparent" || a.startsWith("rgba") || b.startsWith("rgba")) return a !== b;
|
|
@@ -15348,7 +15784,7 @@ function colorsIn(src) {
|
|
|
15348
15784
|
return [...hex, ...rgb, ...hsl];
|
|
15349
15785
|
}
|
|
15350
15786
|
async function extractStatic(url, opts = {}) {
|
|
15351
|
-
const fetchImpl = opts.fetchImpl ??
|
|
15787
|
+
const fetchImpl = opts.fetchImpl ?? guardedFetchLike;
|
|
15352
15788
|
const warnings = [];
|
|
15353
15789
|
const page = await fetchText(url, fetchImpl);
|
|
15354
15790
|
if (!page.ok || !page.text) throw new Error(`Could not fetch ${url} (status ${page.status}). Nothing was extracted.`);
|
|
@@ -15777,6 +16213,7 @@ var init_extract = __esm({
|
|
|
15777
16213
|
init_schema();
|
|
15778
16214
|
init_store();
|
|
15779
16215
|
init_svg_sanitize();
|
|
16216
|
+
init_net_guard();
|
|
15780
16217
|
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36 GHLCommand-DesignStudio/0.1";
|
|
15781
16218
|
rank2 = (arr) => {
|
|
15782
16219
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -16655,9 +17092,9 @@ function removalHoldBack(opts) {
|
|
|
16655
17092
|
if (opts.multiPageStepNames.length > 0) return `${opts.multiPageStepNames.length} step(s) carry more than one page (${opts.multiPageStepNames.join(", ")}) and only the first of each was rebuilt`;
|
|
16656
17093
|
return null;
|
|
16657
17094
|
}
|
|
16658
|
-
function brandedPath(
|
|
17095
|
+
function brandedPath(path45, name, taken) {
|
|
16659
17096
|
const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
16660
|
-
const base = normalize2(
|
|
17097
|
+
const base = normalize2(path45 ?? "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
|
|
16661
17098
|
const used = new Set([...taken].map(normalize2));
|
|
16662
17099
|
let candidate = `${base}-branded`;
|
|
16663
17100
|
for (let n = 2; used.has(candidate); n++) candidate = `${base}-branded-${n}`;
|
|
@@ -16685,9 +17122,9 @@ function rewriteTemplateLinks(envelope, destinations) {
|
|
|
16685
17122
|
representation = "page";
|
|
16686
17123
|
matches = destinations.filter((d2) => d2.pageId === preview[2]);
|
|
16687
17124
|
} else if (!/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(from) && !from.startsWith("#")) {
|
|
16688
|
-
const [
|
|
16689
|
-
suffix = from.slice(
|
|
16690
|
-
matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(
|
|
17125
|
+
const [path45] = from.split(/[?#]/);
|
|
17126
|
+
suffix = from.slice(path45.length);
|
|
17127
|
+
matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(path45));
|
|
16691
17128
|
}
|
|
16692
17129
|
}
|
|
16693
17130
|
if (matches.length !== 1) {
|
|
@@ -16770,8 +17207,8 @@ function registerPageStudioTools(server2, builderClient) {
|
|
|
16770
17207
|
}
|
|
16771
17208
|
return { map: out, truncated };
|
|
16772
17209
|
}
|
|
16773
|
-
async function funnelRequest(method,
|
|
16774
|
-
const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${
|
|
17210
|
+
async function funnelRequest(method, path45, body) {
|
|
17211
|
+
const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path45}`, {
|
|
16775
17212
|
...body ? { body } : {},
|
|
16776
17213
|
headers: {
|
|
16777
17214
|
// Internal-API calls require the app origin (autosave needs the
|
|
@@ -16780,7 +17217,7 @@ function registerPageStudioTools(server2, builderClient) {
|
|
|
16780
17217
|
Referer: "https://app.gohighlevel.com/"
|
|
16781
17218
|
}
|
|
16782
17219
|
});
|
|
16783
|
-
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
17220
|
+
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path45}
|
|
16784
17221
|
${await response.text()}`);
|
|
16785
17222
|
const text2 = await response.text();
|
|
16786
17223
|
return text2 ? JSON.parse(text2) : {};
|
|
@@ -17018,9 +17455,9 @@ ${await response.text()}`);
|
|
|
17018
17455
|
if (!step.pageId) throw new Error("this step has no page to rebuild");
|
|
17019
17456
|
const meta = await funnelRequest("GET", `/page/${step.pageId}?locationId=${client.locationId}`);
|
|
17020
17457
|
if (typeof meta.pageDataDownloadUrl !== "string" || !meta.pageDataDownloadUrl) throw new Error("GoHighLevel has no saved content for this page yet");
|
|
17021
|
-
const response = await
|
|
17458
|
+
const response = await pageStudioNet.read(meta.pageDataDownloadUrl, { signal: AbortSignal.timeout(3e4), maxBytes: 5e7 });
|
|
17022
17459
|
if (!response.ok) throw new Error(`page content read failed (${response.status})`);
|
|
17023
|
-
loaded.push({ ...step, envelope: await response.
|
|
17460
|
+
loaded.push({ ...step, envelope: JSON.parse(await response.text()) });
|
|
17024
17461
|
} catch (e) {
|
|
17025
17462
|
throw new Error(`${step.name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
17026
17463
|
}
|
|
@@ -17029,7 +17466,8 @@ ${await response.text()}`);
|
|
|
17029
17466
|
const controller = new AbortController();
|
|
17030
17467
|
const timer = setTimeout(() => controller.abort(), 3e4);
|
|
17031
17468
|
try {
|
|
17032
|
-
|
|
17469
|
+
const r = await pageStudioNet.read(url, { signal: controller.signal, headers: { "Cache-Control": "no-cache" }, maxBytes: 5e7 });
|
|
17470
|
+
return { ok: r.ok, status: r.status, text: () => r.text(), json: async () => JSON.parse(await r.text()) };
|
|
17033
17471
|
} finally {
|
|
17034
17472
|
clearTimeout(timer);
|
|
17035
17473
|
}
|
|
@@ -17126,9 +17564,9 @@ ${await response.text()}`);
|
|
|
17126
17564
|
await new Promise((r) => setTimeout(r, 1200));
|
|
17127
17565
|
}
|
|
17128
17566
|
if (typeof after.pageDataDownloadUrl !== "string" || !after.pageDataDownloadUrl) throw new Error("the fresh page saved but GoHighLevel is not serving it");
|
|
17129
|
-
const response = await
|
|
17567
|
+
const response = await pageStudioNet.read(after.pageDataDownloadUrl, { signal: AbortSignal.timeout(3e4), maxBytes: 5e7 });
|
|
17130
17568
|
if (!response.ok) throw new Error(`saved content read failed (${response.status})`);
|
|
17131
|
-
const saved = await response.
|
|
17569
|
+
const saved = JSON.parse(await response.text());
|
|
17132
17570
|
storedByPage.set(step.newPageId, saved);
|
|
17133
17571
|
const savedText = JSON.stringify(saved).toLowerCase();
|
|
17134
17572
|
if (formId && forms.length) {
|
|
@@ -17545,7 +17983,7 @@ ${await response.text()}`);
|
|
|
17545
17983
|
}
|
|
17546
17984
|
);
|
|
17547
17985
|
}
|
|
17548
|
-
var import_zod43, crypto4, valueCardSchema;
|
|
17986
|
+
var import_zod43, crypto4, pageStudioNet, valueCardSchema;
|
|
17549
17987
|
var init_page_studio = __esm({
|
|
17550
17988
|
"src/tools/page-studio.ts"() {
|
|
17551
17989
|
"use strict";
|
|
@@ -17562,6 +18000,8 @@ var init_page_studio = __esm({
|
|
|
17562
18000
|
init_tool_helpers();
|
|
17563
18001
|
init_compose();
|
|
17564
18002
|
init_website();
|
|
18003
|
+
init_net_guard();
|
|
18004
|
+
pageStudioNet = { read: guardedFetchLike };
|
|
17565
18005
|
valueCardSchema = import_zod43.z.object({
|
|
17566
18006
|
title: import_zod43.z.string().describe("Card title (short, uppercase reads best)."),
|
|
17567
18007
|
bodyHtml: import_zod43.z.string().describe("Card body as HTML (<p>\u2026</p>). May use {{location.*}} merge fields."),
|
|
@@ -17649,13 +18089,13 @@ function cleanFormDataForSave(formData) {
|
|
|
17649
18089
|
const cleaned = dropRedundantCaseCollidingKeys(formData);
|
|
17650
18090
|
return isJsonObject(cleaned) ? cleaned : formData;
|
|
17651
18091
|
}
|
|
17652
|
-
async function formApiRequest(client, method,
|
|
17653
|
-
const url = `https://backend.leadconnectorhq.com/forms${
|
|
18092
|
+
async function formApiRequest(client, method, path45, body) {
|
|
18093
|
+
const url = `https://backend.leadconnectorhq.com/forms${path45}`;
|
|
17654
18094
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
17655
18095
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
17656
18096
|
if (!response.ok) {
|
|
17657
18097
|
const text3 = await response.text();
|
|
17658
|
-
throw new Error(`Form API Error ${response.status}: ${method} ${
|
|
18098
|
+
throw new Error(`Form API Error ${response.status}: ${method} ${path45}
|
|
17659
18099
|
${text3}`);
|
|
17660
18100
|
}
|
|
17661
18101
|
const text2 = await response.text();
|
|
@@ -17697,7 +18137,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
17697
18137
|
);
|
|
17698
18138
|
return;
|
|
17699
18139
|
}
|
|
17700
|
-
const formRequest = (method,
|
|
18140
|
+
const formRequest = (method, path45, body) => formApiRequest(client, method, path45, body);
|
|
17701
18141
|
server2.tool(
|
|
17702
18142
|
"get_form_full",
|
|
17703
18143
|
"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 what the form builder UI shows, with one exception: where GoHighLevel stores the same value under two spellings of one key that differ only in case (it writes custom fields with both `Id` and `id`), only the lowercase one is returned. No value is lost, and the result can be edited and passed straight back to update_form.",
|
|
@@ -17831,10 +18271,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
17831
18271
|
},
|
|
17832
18272
|
async ({ formId, limit, skip }) => {
|
|
17833
18273
|
try {
|
|
17834
|
-
let
|
|
17835
|
-
if (formId)
|
|
17836
|
-
if (skip)
|
|
17837
|
-
const result = await formRequest("GET",
|
|
18274
|
+
let path45 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
|
|
18275
|
+
if (formId) path45 += `&formId=${formId}`;
|
|
18276
|
+
if (skip) path45 += `&skip=${skip}`;
|
|
18277
|
+
const result = await formRequest("GET", path45);
|
|
17838
18278
|
return {
|
|
17839
18279
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
17840
18280
|
};
|
|
@@ -17966,12 +18406,8 @@ async function readWorkflow(bc, workflowId) {
|
|
|
17966
18406
|
}
|
|
17967
18407
|
}
|
|
17968
18408
|
async function submit(url, body) {
|
|
17969
|
-
const res = await
|
|
17970
|
-
|
|
17971
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
17972
|
-
body: JSON.stringify(body)
|
|
17973
|
-
});
|
|
17974
|
-
const text2 = (await res.text().catch(() => "")).slice(0, 300);
|
|
18409
|
+
const res = await guardedPost(url, JSON.stringify(body), { "Content-Type": "application/json", Accept: "application/json" });
|
|
18410
|
+
const text2 = res.text.slice(0, 300);
|
|
17975
18411
|
return { httpStatus: res.status, blockedByCaptcha: res.status === 403 && /captcha|turnstile/i.test(text2), text: text2 };
|
|
17976
18412
|
}
|
|
17977
18413
|
function registerFunnelQaTools(server2, client, builderClient) {
|
|
@@ -18226,6 +18662,7 @@ var init_funnel_qa = __esm({
|
|
|
18226
18662
|
"use strict";
|
|
18227
18663
|
import_zod45 = require("zod");
|
|
18228
18664
|
init_tool_helpers();
|
|
18665
|
+
init_net_guard();
|
|
18229
18666
|
asObj = (v) => v && typeof v === "object" ? v : {};
|
|
18230
18667
|
asArr = (v) => Array.isArray(v) ? v : [];
|
|
18231
18668
|
str = (v) => typeof v === "string" ? v : void 0;
|
|
@@ -18242,13 +18679,13 @@ var init_funnel_qa = __esm({
|
|
|
18242
18679
|
function registerPipelineBuilderTools(server2, builderClient) {
|
|
18243
18680
|
if (!builderClient) return;
|
|
18244
18681
|
const client = builderClient;
|
|
18245
|
-
async function pipelineRequest(method,
|
|
18246
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
18682
|
+
async function pipelineRequest(method, path45, body) {
|
|
18683
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path45}`;
|
|
18247
18684
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT" || method === "PATCH");
|
|
18248
18685
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
18249
18686
|
if (!response.ok) {
|
|
18250
18687
|
const text3 = await response.text();
|
|
18251
|
-
throw new Error(`Pipeline API Error ${response.status}: ${method} ${
|
|
18688
|
+
throw new Error(`Pipeline API Error ${response.status}: ${method} ${path45}
|
|
18252
18689
|
${text3}`);
|
|
18253
18690
|
}
|
|
18254
18691
|
const text2 = await response.text();
|
|
@@ -19064,9 +19501,6 @@ var init_telemetry_transport = __esm({
|
|
|
19064
19501
|
});
|
|
19065
19502
|
|
|
19066
19503
|
// src/interactive-capture.ts
|
|
19067
|
-
function chromeProfileDir() {
|
|
19068
|
-
return path9.join(appDataDir(), "chrome-profile");
|
|
19069
|
-
}
|
|
19070
19504
|
function captureStatePath() {
|
|
19071
19505
|
return path9.join(appDataDir(), "interactive-capture.json");
|
|
19072
19506
|
}
|
|
@@ -19235,9 +19669,9 @@ function planFromPayload(payload) {
|
|
|
19235
19669
|
return payload?.plan === "command-os" ? "command-os" : "mcp";
|
|
19236
19670
|
}
|
|
19237
19671
|
function legacyDeviceFingerprint() {
|
|
19238
|
-
const
|
|
19672
|
+
const os9 = require("node:os");
|
|
19239
19673
|
const crypto9 = require("node:crypto");
|
|
19240
|
-
const raw = `${
|
|
19674
|
+
const raw = `${os9.hostname()}:${os9.userInfo().username}:${os9.platform()}:${os9.arch()}`;
|
|
19241
19675
|
return crypto9.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
19242
19676
|
}
|
|
19243
19677
|
function deviceFingerprint(opts) {
|
|
@@ -19277,7 +19711,7 @@ function base64urlDecode(s) {
|
|
|
19277
19711
|
async function getPublicKey() {
|
|
19278
19712
|
if (cachedPublicKey) return cachedPublicKey;
|
|
19279
19713
|
const raw = Buffer.from(ATTESTATION_PUBLIC_KEY_B64, "base64");
|
|
19280
|
-
const key = await
|
|
19714
|
+
const key = await import_node_crypto4.webcrypto.subtle.importKey(
|
|
19281
19715
|
"raw",
|
|
19282
19716
|
raw,
|
|
19283
19717
|
{ name: "Ed25519" },
|
|
@@ -19325,7 +19759,7 @@ async function verifyAttestation(token2, bindings, now = /* @__PURE__ */ new Dat
|
|
|
19325
19759
|
let signatureValid;
|
|
19326
19760
|
try {
|
|
19327
19761
|
const key = await getPublicKey();
|
|
19328
|
-
signatureValid = await
|
|
19762
|
+
signatureValid = await import_node_crypto4.webcrypto.subtle.verify({ name: "Ed25519" }, key, signatureBytes, payloadBytes);
|
|
19329
19763
|
} catch {
|
|
19330
19764
|
return { ok: false, reason: "bad-signature" };
|
|
19331
19765
|
}
|
|
@@ -19357,11 +19791,11 @@ function shouldRenew(payload, now = /* @__PURE__ */ new Date()) {
|
|
|
19357
19791
|
const remainingMs = expiry - now.getTime();
|
|
19358
19792
|
return remainingMs < 7 * 24 * 60 * 60 * 1e3;
|
|
19359
19793
|
}
|
|
19360
|
-
var
|
|
19794
|
+
var import_node_crypto4, ATTESTATION_PUBLIC_KEY_B64, ATTESTATION_VERSION, ATTESTATION_TTL_DAYS, ATTESTATION_GRACE_DAYS, cachedPublicKey;
|
|
19361
19795
|
var init_attestation = __esm({
|
|
19362
19796
|
"src/attestation.ts"() {
|
|
19363
19797
|
"use strict";
|
|
19364
|
-
|
|
19798
|
+
import_node_crypto4 = require("node:crypto");
|
|
19365
19799
|
ATTESTATION_PUBLIC_KEY_B64 = "8KJo8V3Z7ngeToIQfOXpKdlr/EA34hXJVEIpW0A7wqs=";
|
|
19366
19800
|
ATTESTATION_VERSION = 1;
|
|
19367
19801
|
ATTESTATION_TTL_DAYS = 30;
|
|
@@ -20694,23 +21128,23 @@ function moneySummary(subs, invoices, charges, products, now = /* @__PURE__ */ n
|
|
|
20694
21128
|
}
|
|
20695
21129
|
}
|
|
20696
21130
|
const nowS = now.getTime() / 1e3;
|
|
20697
|
-
const
|
|
21131
|
+
const within2 = (t, days) => t <= nowS && nowS - t <= days * 86400;
|
|
20698
21132
|
const r7 = /* @__PURE__ */ new Map(), r30 = /* @__PURE__ */ new Map();
|
|
20699
21133
|
let c7 = 0, c30 = 0, failed30 = 0;
|
|
20700
21134
|
for (const c of charges) {
|
|
20701
|
-
if (!
|
|
21135
|
+
if (!within2(c.created, 30)) continue;
|
|
20702
21136
|
if (c.status === "failed") {
|
|
20703
21137
|
failed30++;
|
|
20704
21138
|
continue;
|
|
20705
21139
|
}
|
|
20706
21140
|
if (c.status !== "succeeded") continue;
|
|
20707
21141
|
const currency = upper(c.currency);
|
|
20708
|
-
const
|
|
20709
|
-
if (
|
|
20710
|
-
add(r30, currency,
|
|
21142
|
+
const net2 = fromMinor(c.amount - (c.amount_refunded ?? 0), currency) ?? 0;
|
|
21143
|
+
if (net2 <= 0) continue;
|
|
21144
|
+
add(r30, currency, net2);
|
|
20711
21145
|
c30++;
|
|
20712
|
-
if (
|
|
20713
|
-
add(r7, currency,
|
|
21146
|
+
if (within2(c.created, 7)) {
|
|
21147
|
+
add(r7, currency, net2);
|
|
20714
21148
|
c7++;
|
|
20715
21149
|
}
|
|
20716
21150
|
}
|
|
@@ -20718,7 +21152,7 @@ function moneySummary(subs, invoices, charges, products, now = /* @__PURE__ */ n
|
|
|
20718
21152
|
for (const i of invoices) {
|
|
20719
21153
|
const paidAt = i.status_transitions?.paid_at;
|
|
20720
21154
|
const subId = i.parent?.subscription_details?.subscription ?? i.subscription;
|
|
20721
|
-
if (i.status !== "paid" || !paidAt || !
|
|
21155
|
+
if (i.status !== "paid" || !paidAt || !within2(paidAt, 30) || !subId) continue;
|
|
20722
21156
|
const currency = upper(i.currency);
|
|
20723
21157
|
const paid = fromMinor(i.amount_paid, currency);
|
|
20724
21158
|
if (currency && paid) add(recurring, currency, paid);
|
|
@@ -20789,7 +21223,7 @@ function saveCalendarSecret(secret) {
|
|
|
20789
21223
|
const validated = SecretSchema.parse(secret);
|
|
20790
21224
|
const dir = ensureAppDataDir();
|
|
20791
21225
|
if (process.platform !== "win32") fs12.chmodSync(dir, 448);
|
|
20792
|
-
tmp = `${googleCalendarPath()}.${(0,
|
|
21226
|
+
tmp = `${googleCalendarPath()}.${(0, import_node_crypto5.randomUUID)()}.tmp`;
|
|
20793
21227
|
fs12.writeFileSync(tmp, JSON.stringify(validated) + "\n", { mode: 384, flag: "wx" });
|
|
20794
21228
|
fs12.renameSync(tmp, googleCalendarPath());
|
|
20795
21229
|
} catch {
|
|
@@ -20812,13 +21246,13 @@ function removeCalendarSecret() {
|
|
|
20812
21246
|
throw new Error("The Google Calendar sign-in could not be removed from this machine.");
|
|
20813
21247
|
}
|
|
20814
21248
|
}
|
|
20815
|
-
var fs12, path11,
|
|
21249
|
+
var fs12, path11, import_node_crypto5, import_zod51, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
|
|
20816
21250
|
var init_google_calendar_store = __esm({
|
|
20817
21251
|
"src/command-os/connectors/google-calendar-store.ts"() {
|
|
20818
21252
|
"use strict";
|
|
20819
21253
|
fs12 = __toESM(require("node:fs"));
|
|
20820
21254
|
path11 = __toESM(require("node:path"));
|
|
20821
|
-
|
|
21255
|
+
import_node_crypto5 = require("node:crypto");
|
|
20822
21256
|
import_zod51 = require("zod");
|
|
20823
21257
|
init_credentials_store();
|
|
20824
21258
|
CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
@@ -20951,16 +21385,16 @@ function createGoogleCalendar(deps = {}) {
|
|
|
20951
21385
|
requireOnlyCalendar(result.scope);
|
|
20952
21386
|
return result.access_token;
|
|
20953
21387
|
}
|
|
20954
|
-
async function pages(
|
|
21388
|
+
async function pages(path45, access, item, query = {}) {
|
|
20955
21389
|
const result = [];
|
|
20956
21390
|
const seen = /* @__PURE__ */ new Set();
|
|
20957
21391
|
let pageToken;
|
|
20958
21392
|
for (let page = 0; page < 100; page++) {
|
|
20959
|
-
const url = new URL(`${API}${
|
|
21393
|
+
const url = new URL(`${API}${path45}`);
|
|
20960
21394
|
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
|
20961
21395
|
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
|
20962
21396
|
const data = parsed(
|
|
20963
|
-
import_zod52.z.object({ items: import_zod52.z.array(item).optional(), nextPageToken: import_zod52.z.string().optional(), kind: import_zod52.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (
|
|
21397
|
+
import_zod52.z.object({ items: import_zod52.z.array(item).optional(), nextPageToken: import_zod52.z.string().optional(), kind: import_zod52.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path45.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
|
|
20964
21398
|
await request(url.href, { headers: { Authorization: `Bearer ${access}` } })
|
|
20965
21399
|
);
|
|
20966
21400
|
result.push(...data.items ?? []);
|
|
@@ -20978,9 +21412,9 @@ function createGoogleCalendar(deps = {}) {
|
|
|
20978
21412
|
async function start() {
|
|
20979
21413
|
const app = appConfig();
|
|
20980
21414
|
clearPending2();
|
|
20981
|
-
const state = (0,
|
|
20982
|
-
const verifier = (0,
|
|
20983
|
-
const server2 = (deps.createServer ??
|
|
21415
|
+
const state = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
|
|
21416
|
+
const verifier = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
|
|
21417
|
+
const server2 = (deps.createServer ?? import_node_http2.createServer)((req, res) => {
|
|
20984
21418
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
20985
21419
|
res.setHeader("Cache-Control", "no-store");
|
|
20986
21420
|
res.setHeader("Referrer-Policy", "no-referrer");
|
|
@@ -21033,7 +21467,7 @@ function createGoogleCalendar(deps = {}) {
|
|
|
21033
21467
|
response_type: "code",
|
|
21034
21468
|
scope: CALENDAR_SCOPE,
|
|
21035
21469
|
state,
|
|
21036
|
-
code_challenge: (0,
|
|
21470
|
+
code_challenge: (0, import_node_crypto6.createHash)("sha256").update(verifier).digest("base64url"),
|
|
21037
21471
|
code_challenge_method: "S256",
|
|
21038
21472
|
access_type: "offline",
|
|
21039
21473
|
prompt: "consent"
|
|
@@ -21163,13 +21597,13 @@ function createGoogleCalendar(deps = {}) {
|
|
|
21163
21597
|
close: clearPending2
|
|
21164
21598
|
};
|
|
21165
21599
|
}
|
|
21166
|
-
var
|
|
21600
|
+
var import_node_http2, import_node_crypto6, import_node_child_process, import_zod52, TOKEN_URL, REVOKE_URL, API, tokenSchema, calendarSchema, eventSchema;
|
|
21167
21601
|
var init_google_calendar = __esm({
|
|
21168
21602
|
"src/command-os/connectors/google-calendar.ts"() {
|
|
21169
21603
|
"use strict";
|
|
21170
21604
|
init_any_signal();
|
|
21171
|
-
|
|
21172
|
-
|
|
21605
|
+
import_node_http2 = require("node:http");
|
|
21606
|
+
import_node_crypto6 = require("node:crypto");
|
|
21173
21607
|
import_node_child_process = require("node:child_process");
|
|
21174
21608
|
import_zod52 = require("zod");
|
|
21175
21609
|
init_credentials_store();
|
|
@@ -21211,8 +21645,8 @@ function redactConnectionKey(value, key) {
|
|
|
21211
21645
|
return value;
|
|
21212
21646
|
}
|
|
21213
21647
|
function stripeFetcher(key, signal) {
|
|
21214
|
-
return async (
|
|
21215
|
-
const url = new URL(`https://api.stripe.com${
|
|
21648
|
+
return async (path45, query) => {
|
|
21649
|
+
const url = new URL(`https://api.stripe.com${path45}`);
|
|
21216
21650
|
for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
|
|
21217
21651
|
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` }, signal });
|
|
21218
21652
|
if (!res.ok) {
|
|
@@ -21301,11 +21735,11 @@ var init_handlers = __esm({
|
|
|
21301
21735
|
});
|
|
21302
21736
|
|
|
21303
21737
|
// src/command-os/connectors/stripe-read.ts
|
|
21304
|
-
async function listAll(f,
|
|
21738
|
+
async function listAll(f, path45, schema, extra = {}) {
|
|
21305
21739
|
const out = [];
|
|
21306
21740
|
let after;
|
|
21307
21741
|
for (let page = 0; page < 20; page++) {
|
|
21308
|
-
const result = import_zod53.z.object({ data: import_zod53.z.array(schema), has_more: import_zod53.z.boolean().optional() }).parse(await f(
|
|
21742
|
+
const result = import_zod53.z.object({ data: import_zod53.z.array(schema), has_more: import_zod53.z.boolean().optional() }).parse(await f(path45, { limit: "100", ...extra, ...after ? { starting_after: after } : {} }));
|
|
21309
21743
|
out.push(...result.data);
|
|
21310
21744
|
if (!result.has_more) return out;
|
|
21311
21745
|
const last = import_zod53.z.object({ id: import_zod53.z.string().min(1) }).parse(result.data.at(-1));
|
|
@@ -28562,7 +28996,7 @@ function remember(id, readAt) {
|
|
|
28562
28996
|
const times = readTimes();
|
|
28563
28997
|
times[id] = readAt;
|
|
28564
28998
|
ensureAppDataDir();
|
|
28565
|
-
const tmp = `${timesPath()}.${(0,
|
|
28999
|
+
const tmp = `${timesPath()}.${(0, import_node_crypto7.randomUUID)()}.tmp`;
|
|
28566
29000
|
try {
|
|
28567
29001
|
fs21.writeFileSync(tmp, JSON.stringify(times) + "\n", { mode: 384, flag: "wx" });
|
|
28568
29002
|
fs21.renameSync(tmp, timesPath());
|
|
@@ -28615,14 +29049,14 @@ function money2(rows2) {
|
|
|
28615
29049
|
async function readBriefPanels(deps) {
|
|
28616
29050
|
return Promise.all(CONNECTOR_REGISTRY.flatMap((entry) => entry.panel ? [entry.panel(briefPanelDeps(deps))] : []));
|
|
28617
29051
|
}
|
|
28618
|
-
var fs21, path19,
|
|
29052
|
+
var fs21, path19, import_node_crypto7, import_zod57, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, amountText, stripePanel, slackPanel, calendarPanel;
|
|
28619
29053
|
var init_brief_panels = __esm({
|
|
28620
29054
|
"src/command-os/connectors/brief-panels.ts"() {
|
|
28621
29055
|
"use strict";
|
|
28622
29056
|
init_brief_deadline();
|
|
28623
29057
|
fs21 = __toESM(require("node:fs"));
|
|
28624
29058
|
path19 = __toESM(require("node:path"));
|
|
28625
|
-
|
|
29059
|
+
import_node_crypto7 = require("node:crypto");
|
|
28626
29060
|
import_zod57 = require("zod");
|
|
28627
29061
|
init_credentials_store();
|
|
28628
29062
|
init_brief_row();
|
|
@@ -28840,7 +29274,7 @@ var init_client_capabilities = __esm({
|
|
|
28840
29274
|
{ key: "reputation", label: "Reputation and content", blurb: "Reviews, social posts and the blog." },
|
|
28841
29275
|
{ key: "health", label: "Health and handoff", blurb: "Proving it works, keeping it clean, and handing it over." }
|
|
28842
29276
|
];
|
|
28843
|
-
N = (label,
|
|
29277
|
+
N = (label, path45, params, key, nameField, version) => ({ label, path: path45, params, key, nameField, version });
|
|
28844
29278
|
W = (read2, extra) => ({ ...read2, ...extra });
|
|
28845
29279
|
AI2 = "2021-04-15";
|
|
28846
29280
|
CAPABILITIES = [
|
|
@@ -29130,7 +29564,7 @@ var require_package = __commonJS({
|
|
|
29130
29564
|
"package.json"(exports2, module2) {
|
|
29131
29565
|
module2.exports = {
|
|
29132
29566
|
name: "@elitedcs/ghl-mcp",
|
|
29133
|
-
version: "3.88.
|
|
29567
|
+
version: "3.88.3",
|
|
29134
29568
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
29135
29569
|
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.",
|
|
29136
29570
|
main: "dist/index.js",
|
|
@@ -30158,8 +30592,26 @@ async function checkLinks(urls, ownHosts, fetcher, timeoutMs = 1e4) {
|
|
|
30158
30592
|
const ctrl = new AbortController();
|
|
30159
30593
|
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
30160
30594
|
try {
|
|
30161
|
-
|
|
30162
|
-
|
|
30595
|
+
let cur = url;
|
|
30596
|
+
let r = await fetcher(cur, { method: "GET", redirect: "manual", signal: ctrl.signal, headers: { "User-Agent": "Mozilla/5.0 (Command OS secret shopper)" } });
|
|
30597
|
+
let stopped = null;
|
|
30598
|
+
for (let hop = 0; r.status >= 300 && r.status < 400 && r.location; hop++) {
|
|
30599
|
+
let next;
|
|
30600
|
+
try {
|
|
30601
|
+
next = new URL(r.location, cur).toString();
|
|
30602
|
+
} catch {
|
|
30603
|
+
stopped = "It redirects to an address that is not readable, so it was not followed.";
|
|
30604
|
+
break;
|
|
30605
|
+
}
|
|
30606
|
+
const why = hop >= MAX_LINK_HOPS ? "it redirects more than 5 times" : skipReason(next, ownHosts)?.replace(/^Not opened: /, "") ?? null;
|
|
30607
|
+
if (why) {
|
|
30608
|
+
stopped = `It redirects to ${next}, which was not opened: ${why}`;
|
|
30609
|
+
break;
|
|
30610
|
+
}
|
|
30611
|
+
cur = next;
|
|
30612
|
+
r = await fetcher(cur, { method: "GET", redirect: "manual", signal: ctrl.signal, headers: { "User-Agent": "Mozilla/5.0 (Command OS secret shopper)" } });
|
|
30613
|
+
}
|
|
30614
|
+
results.push(stopped ? { url, state: "skipped", detail: stopped } : r.status < 400 ? { url, state: "ok", detail: `Opened (HTTP ${r.status}).` } : { url, state: "broken", detail: `The page answered HTTP ${r.status}.` });
|
|
30163
30615
|
} catch (e) {
|
|
30164
30616
|
const why = ctrl.signal.aborted ? `It did not answer within ${Math.round(timeoutMs / 1e3)} seconds.` : `It could not be reached (${e instanceof Error ? e.message : String(e)}).`;
|
|
30165
30617
|
results.push({ url, state: "broken", detail: why });
|
|
@@ -30173,7 +30625,7 @@ function htmlToText(body) {
|
|
|
30173
30625
|
if (!/<(?:p|div|br|table|td|span|a|html|body|h\d|li|style|strong|em|b|i)(?:\s+[a-z][a-z0-9:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'<>=]+))?)*\s*\/?>/i.test(body)) return body;
|
|
30174
30626
|
return body.replace(/<(style|script|head)\b[\s\S]*?<\/\1>/gi, " ").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(?:p|div|tr|li|h\d|table)>/gi, "\n").replace(/<\/(?:td|th)>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /gi, "\xA0").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'|'/gi, "'").replace(/&#x([0-9a-f]+);/gi, (_m, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_m, d) => String.fromCodePoint(Number(d))).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
30175
30627
|
}
|
|
30176
|
-
var URL_RE, HREF_RE, TRACKING_PATH, UNSUB;
|
|
30628
|
+
var URL_RE, HREF_RE, TRACKING_PATH, UNSUB, MAX_LINK_HOPS;
|
|
30177
30629
|
var init_links = __esm({
|
|
30178
30630
|
"src/command-os/shopper/links.ts"() {
|
|
30179
30631
|
"use strict";
|
|
@@ -30181,6 +30633,7 @@ var init_links = __esm({
|
|
|
30181
30633
|
HREF_RE = /\bhref\s*=\s*["']([^"']+)["']/gi;
|
|
30182
30634
|
TRACKING_PATH = /\/(?:c|o|l|ls|tl|t|r|e|wf|track|click|open)\/|\/ls\/click|[?&](?:upn|trigger_link|utm_id|mc_cid|_hs(?:enc|mi))=/i;
|
|
30183
30635
|
UNSUB = /unsubscribe|opt-?out|\/preferences|email-preferences|unsubscribe-view|\/emails\/builder\//i;
|
|
30636
|
+
MAX_LINK_HOPS = 5;
|
|
30184
30637
|
}
|
|
30185
30638
|
});
|
|
30186
30639
|
|
|
@@ -30483,7 +30936,7 @@ function planCopy(src, opts) {
|
|
|
30483
30936
|
const idMap = /* @__PURE__ */ new Map();
|
|
30484
30937
|
for (const n of nodes) {
|
|
30485
30938
|
const id = str4(n.id);
|
|
30486
|
-
if (id) idMap.set(id,
|
|
30939
|
+
if (id) idMap.set(id, import_node_crypto8.default.randomUUID());
|
|
30487
30940
|
}
|
|
30488
30941
|
const remap = (id) => typeof id === "string" ? idMap.get(id) ?? id : id;
|
|
30489
30942
|
const waits = [];
|
|
@@ -30736,13 +31189,13 @@ function builderAdmin(b) {
|
|
|
30736
31189
|
remove: (id, ack) => b.deleteWorkflow(id, ack ? { acknowledgeContacts: ack } : void 0)
|
|
30737
31190
|
};
|
|
30738
31191
|
}
|
|
30739
|
-
var import_node_fs2, import_node_path2,
|
|
31192
|
+
var import_node_fs2, import_node_path2, import_node_crypto8, asObj3, asArr2, str4, FF_PREFIX, ALLOWED, hostOf, normalizeHost, newToken, TOKEN_NAME, tokenPrefix, DAY_NAMES, remapSelf, journalFile, SWEEP_MIN_AGE_MS;
|
|
30740
31193
|
var init_fastforward = __esm({
|
|
30741
31194
|
"src/command-os/shopper/fastforward.ts"() {
|
|
30742
31195
|
"use strict";
|
|
30743
31196
|
import_node_fs2 = __toESM(require("node:fs"));
|
|
30744
31197
|
import_node_path2 = __toESM(require("node:path"));
|
|
30745
|
-
|
|
31198
|
+
import_node_crypto8 = __toESM(require("node:crypto"));
|
|
30746
31199
|
init_run();
|
|
30747
31200
|
asObj3 = (v) => v && typeof v === "object" ? v : {};
|
|
30748
31201
|
asArr2 = (v) => Array.isArray(v) ? v : [];
|
|
@@ -30778,7 +31231,7 @@ var init_fastforward = __esm({
|
|
|
30778
31231
|
};
|
|
30779
31232
|
newToken = () => {
|
|
30780
31233
|
let t = "";
|
|
30781
|
-
while (t.length < 6) t += (
|
|
31234
|
+
while (t.length < 6) t += (import_node_crypto8.default.randomBytes(1)[0] % 36).toString(36);
|
|
30782
31235
|
return t;
|
|
30783
31236
|
};
|
|
30784
31237
|
TOKEN_NAME = new RegExp(`^${FF_PREFIX} [0-9a-z]{6} `);
|
|
@@ -30914,7 +31367,7 @@ async function findContactIdByEmail(client, locationId2, email) {
|
|
|
30914
31367
|
async function startShopper(input, deps) {
|
|
30915
31368
|
const now = deps.now ?? Date.now;
|
|
30916
31369
|
const sleep7 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
30917
|
-
const token2 =
|
|
31370
|
+
const token2 = import_node_crypto9.default.randomUUID().replace(/-/g, "").slice(0, 8);
|
|
30918
31371
|
const leadEmail = plusAddress(input.ownerEmail, token2);
|
|
30919
31372
|
if (!SAFE_ID.test(input.locationId)) throw new Error("That client id is not valid.");
|
|
30920
31373
|
if (input.entry.kind === "workflows" && input.entry.workflowIds.length === 0) throw new Error("Choose at least one workflow for the test lead to enter.");
|
|
@@ -31021,7 +31474,7 @@ async function startShopper(input, deps) {
|
|
|
31021
31474
|
}
|
|
31022
31475
|
}
|
|
31023
31476
|
async function postJson(url, body) {
|
|
31024
|
-
const r = await
|
|
31477
|
+
const r = await guardedPost(url, JSON.stringify(body), { "Content-Type": "application/json", Accept: "application/json" });
|
|
31025
31478
|
return r.status;
|
|
31026
31479
|
}
|
|
31027
31480
|
async function removeOwnLead(client, run) {
|
|
@@ -31059,7 +31512,7 @@ function pidAlive(pid) {
|
|
|
31059
31512
|
function claimRun(locationId2, runId, base) {
|
|
31060
31513
|
const lock = `${runPath(locationId2, runId, base)}.lock`;
|
|
31061
31514
|
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(lock), { recursive: true, mode: 448 });
|
|
31062
|
-
const token2 =
|
|
31515
|
+
const token2 = import_node_crypto9.default.randomUUID();
|
|
31063
31516
|
const mine = JSON.stringify({ pid: process.pid, token: token2 });
|
|
31064
31517
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
31065
31518
|
try {
|
|
@@ -31113,7 +31566,7 @@ async function finishShopper(locationId2, runId, deps) {
|
|
|
31113
31566
|
const t = now();
|
|
31114
31567
|
const closed = t >= run.closesAt;
|
|
31115
31568
|
const raw = await readSentMessages(deps.client, run.locationId, run.contactId, run.startedAt);
|
|
31116
|
-
const fetcher = deps.fetcher ?? ((url, init) =>
|
|
31569
|
+
const fetcher = deps.fetcher ?? ((url, init) => guardedFetchLike(url, { headers: init.headers, signal: init.signal, redirect: "manual", statusOnly: true }).then((r) => ({ status: r.status, location: r.headers.get("location") })));
|
|
31117
31570
|
const urls = [...new Set(raw.flatMap((m) => extractUrls(m.body, m.raw !== m.body ? m.raw : void 0)))];
|
|
31118
31571
|
const linkResults = (await checkLinks(urls, run.websiteHost ? [run.websiteHost] : [], fetcher)).map((l) => ({ ...l, url: maskSecrets(l.url) }));
|
|
31119
31572
|
const messages = raw.map((m) => ({ id: m.id, channel: m.channel, at: m.at, source: m.source, body: maskSecrets(m.body), subject: m.subject ? maskSecrets(m.subject) : void 0 }));
|
|
@@ -31189,13 +31642,13 @@ function summarizeRun(run) {
|
|
|
31189
31642
|
if (run.status === "watching") return `So far: ${head4}${stepNote} Still watching until ${new Date(run.closesAt).toISOString()}.`;
|
|
31190
31643
|
return gradeLine + head4 + stepNote;
|
|
31191
31644
|
}
|
|
31192
|
-
var import_node_fs3, import_node_path3,
|
|
31645
|
+
var import_node_fs3, import_node_path3, import_node_crypto9, MAX_EXTEND_MINUTES, asObj4, asArr3, str5, SAFE_ID, notFound;
|
|
31193
31646
|
var init_run = __esm({
|
|
31194
31647
|
"src/command-os/shopper/run.ts"() {
|
|
31195
31648
|
"use strict";
|
|
31196
31649
|
import_node_fs3 = __toESM(require("node:fs"));
|
|
31197
31650
|
import_node_path3 = __toESM(require("node:path"));
|
|
31198
|
-
|
|
31651
|
+
import_node_crypto9 = __toESM(require("node:crypto"));
|
|
31199
31652
|
init_credentials_store();
|
|
31200
31653
|
init_analyze();
|
|
31201
31654
|
init_links();
|
|
@@ -31203,6 +31656,7 @@ var init_run = __esm({
|
|
|
31203
31656
|
init_account_rules();
|
|
31204
31657
|
init_score();
|
|
31205
31658
|
init_steps();
|
|
31659
|
+
init_net_guard();
|
|
31206
31660
|
MAX_EXTEND_MINUTES = 10;
|
|
31207
31661
|
asObj4 = (v) => v && typeof v === "object" ? v : {};
|
|
31208
31662
|
asArr3 = (v) => Array.isArray(v) ? v : [];
|
|
@@ -32402,7 +32856,7 @@ var init_page2 = __esm({
|
|
|
32402
32856
|
nextStep = (c) => c.state === "proven" || c.id === "google-calendar" && c.setup === "needs-setup" ? "" : c.id === "google-calendar" ? "Next: press Connect and sign in with Google." : c.state === "saved-unproven" ? `Next: paste the ${keyWord(c.id)} again and press Connect, so we can check it works.` : `Next: paste your ${keyWord(c.id)} below and press Connect.`;
|
|
32403
32857
|
chip3 = (c, needs) => c.readError ? { tone: "warn", word: "Could not read" } : c.state === "saved-unproven" ? { tone: "warn", word: "Not proven" } : c.state === "not-connected" ? { tone: "", word: "Not connected" } : needs ? { tone: "warn", word: "Needs you today" } : c.state === "proven" ? { tone: "ok", word: "Connected" } : { tone: "", word: "Not connected" };
|
|
32404
32858
|
head = (c, chips) => `<div class="cds-panel-head"><div class="txt"><h2 class="cds-panel-title">${esc4(c.name)}</h2></div><div class="cds-row">${chips}</div></div>`;
|
|
32405
|
-
how = (inner, id,
|
|
32859
|
+
how = (inner, id, hidden2) => `<details class="how-it-works cds-details" id="instructions-${id}"${hidden2 ? " hidden" : ""}><summary>How this connection works</summary><div class="connector-instructions">${inner}</div></details>`;
|
|
32406
32860
|
needsRow = (c, ask) => `<li><a class="ttl" href="#${c.id}">${esc4(c.name)}<span class="sub">${esc4(ask)}</span></a></li>`;
|
|
32407
32861
|
}
|
|
32408
32862
|
});
|
|
@@ -34142,7 +34596,7 @@ var init_client_status_placement = __esm({
|
|
|
34142
34596
|
// src/build-contract.ts
|
|
34143
34597
|
function contentHash(value) {
|
|
34144
34598
|
const canonical2 = (v) => Array.isArray(v) ? v.map(canonical2) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => [k, canonical2(v[k])])) : v;
|
|
34145
|
-
return (0,
|
|
34599
|
+
return (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify(canonical2(value)) ?? "null").digest("hex");
|
|
34146
34600
|
}
|
|
34147
34601
|
function assertBuildApproval(approval, inputHash) {
|
|
34148
34602
|
if (!approval?.at) throw new Error("Approve building before running Build.");
|
|
@@ -34317,11 +34771,11 @@ function executablePlanLines(saved) {
|
|
|
34317
34771
|
for (const w of preview?.workflows ?? []) for (const m of [...w.manual ?? [], ...w.needsContent ?? []]) lines.push(`Unresolved: ${w.name || names.get(w.ref) || "Unnamed workflow"}. ${m.reason}`);
|
|
34318
34772
|
return lines.map(sentence);
|
|
34319
34773
|
}
|
|
34320
|
-
var
|
|
34774
|
+
var import_node_crypto10, import_node_fs5, import_node_path5, INTAKE_CHANGED;
|
|
34321
34775
|
var init_build_contract = __esm({
|
|
34322
34776
|
"src/build-contract.ts"() {
|
|
34323
34777
|
"use strict";
|
|
34324
|
-
|
|
34778
|
+
import_node_crypto10 = require("node:crypto");
|
|
34325
34779
|
import_node_fs5 = require("node:fs");
|
|
34326
34780
|
import_node_path5 = require("node:path");
|
|
34327
34781
|
init_credentials_store();
|
|
@@ -34400,7 +34854,7 @@ async function approvedStatusWriter(registry2, locationId2) {
|
|
|
34400
34854
|
return saved;
|
|
34401
34855
|
},
|
|
34402
34856
|
async readPublishedPage(url) {
|
|
34403
|
-
const result = await
|
|
34857
|
+
const result = await guardedFetchLike(url, { signal: AbortSignal.timeout(15e3) });
|
|
34404
34858
|
if (!result.ok) throw new Error("The published handoff address could not be reached.");
|
|
34405
34859
|
return result.text();
|
|
34406
34860
|
}
|
|
@@ -34416,6 +34870,7 @@ var init_approved_status_writer = __esm({
|
|
|
34416
34870
|
init_funnel_builder();
|
|
34417
34871
|
init_client_status_placement();
|
|
34418
34872
|
init_build_contract();
|
|
34873
|
+
init_net_guard();
|
|
34419
34874
|
}
|
|
34420
34875
|
});
|
|
34421
34876
|
|
|
@@ -34900,7 +35355,7 @@ function engineRepair(version) {
|
|
|
34900
35355
|
return !version ? CLAUDE_REPAIR_MESSAGES[0] : !meetsFloor(version) ? CLAUDE_REPAIR_MESSAGES[1] : CLAUDE_REPAIR_MESSAGES[2];
|
|
34901
35356
|
}
|
|
34902
35357
|
function keyFingerprint(key) {
|
|
34903
|
-
return (0,
|
|
35358
|
+
return (0, import_node_crypto11.createHash)("sha256").update(key).digest("hex");
|
|
34904
35359
|
}
|
|
34905
35360
|
async function clientPreflight(registry2, locationId2) {
|
|
34906
35361
|
const lines = [];
|
|
@@ -34967,7 +35422,7 @@ async function agencyConnectionReady(registry2, companyId) {
|
|
|
34967
35422
|
return false;
|
|
34968
35423
|
}
|
|
34969
35424
|
}
|
|
34970
|
-
var
|
|
35425
|
+
var import_node_crypto11, CLAUDE_REPAIR_MESSAGES;
|
|
34971
35426
|
var init_client_preflight = __esm({
|
|
34972
35427
|
"src/client-preflight.ts"() {
|
|
34973
35428
|
"use strict";
|
|
@@ -34977,7 +35432,7 @@ var init_client_preflight = __esm({
|
|
|
34977
35432
|
init_stage_runner();
|
|
34978
35433
|
init_tool_filter();
|
|
34979
35434
|
init_credentials_store();
|
|
34980
|
-
|
|
35435
|
+
import_node_crypto11 = require("node:crypto");
|
|
34981
35436
|
CLAUDE_REPAIR_MESSAGES = [
|
|
34982
35437
|
"Install Claude Code from Your agency before reading this source.",
|
|
34983
35438
|
"Update Claude Code to version 2.1.0 or newer from Your agency.",
|
|
@@ -35084,7 +35539,7 @@ function startDesignRun(request, opts = {}) {
|
|
|
35084
35539
|
const checked = validateRunRequest(request);
|
|
35085
35540
|
if (!checked.ok) throw new Error(checked.error);
|
|
35086
35541
|
request = checked.value;
|
|
35087
|
-
const id = (0,
|
|
35542
|
+
const id = (0, import_node_crypto12.randomUUID)();
|
|
35088
35543
|
const outDir = (0, import_node_path7.join)(designRunsDir(), id);
|
|
35089
35544
|
(0, import_node_fs7.mkdirSync)(outDir, { recursive: true });
|
|
35090
35545
|
const job = { id, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), outDir, request, tail: [] };
|
|
@@ -35151,14 +35606,14 @@ function cancelDesignRun(id) {
|
|
|
35151
35606
|
}
|
|
35152
35607
|
return true;
|
|
35153
35608
|
}
|
|
35154
|
-
var import_node_child_process3, import_node_fs7, import_node_path7,
|
|
35609
|
+
var import_node_child_process3, import_node_fs7, import_node_path7, import_node_crypto12, SLUG2, MODEL, JOINED_FLAG, jobs, children;
|
|
35155
35610
|
var init_run_jobs = __esm({
|
|
35156
35611
|
"src/command-os/design/run-jobs.ts"() {
|
|
35157
35612
|
"use strict";
|
|
35158
35613
|
import_node_child_process3 = require("node:child_process");
|
|
35159
35614
|
import_node_fs7 = require("node:fs");
|
|
35160
35615
|
import_node_path7 = require("node:path");
|
|
35161
|
-
|
|
35616
|
+
import_node_crypto12 = require("node:crypto");
|
|
35162
35617
|
init_credentials_store();
|
|
35163
35618
|
init_schema();
|
|
35164
35619
|
init_brief_validation();
|
|
@@ -36037,9 +36492,9 @@ function presetForBusinessType(type) {
|
|
|
36037
36492
|
return "generic";
|
|
36038
36493
|
}
|
|
36039
36494
|
}
|
|
36040
|
-
function setPath(target,
|
|
36495
|
+
function setPath(target, path45, value) {
|
|
36041
36496
|
if (value === void 0) return;
|
|
36042
|
-
const parts =
|
|
36497
|
+
const parts = path45.split(".");
|
|
36043
36498
|
let node = target;
|
|
36044
36499
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
36045
36500
|
const k = parts[i];
|
|
@@ -38162,9 +38617,9 @@ function recorderAllowPatterns(mcpListOutput) {
|
|
|
38162
38617
|
function prefillTools(source, recorderPatterns = []) {
|
|
38163
38618
|
const deny = "Bash,Write,Edit,MultiEdit,NotebookEdit,Task";
|
|
38164
38619
|
if (source.kind === "text") return { allow: "", deny: deny + ",WebFetch,WebSearch,Read,Glob,Grep" };
|
|
38165
|
-
if (source.kind === "url") return { allow: "
|
|
38620
|
+
if (source.kind === "url") return { allow: "", deny: deny + ",WebFetch,WebSearch,Read,Glob,Grep" };
|
|
38166
38621
|
if (source.kind === "file") return { allow: "Read", deny: deny + ",WebFetch,WebSearch,Glob,Grep" };
|
|
38167
|
-
return { allow: recorderPatterns.join(","), deny: deny + ",Read,Glob,Grep" };
|
|
38622
|
+
return { allow: recorderPatterns.join(","), deny: deny + ",WebFetch,WebSearch,Read,Glob,Grep" };
|
|
38168
38623
|
}
|
|
38169
38624
|
function sourcePreamble(source) {
|
|
38170
38625
|
if (source.kind === "file") {
|
|
@@ -38211,6 +38666,7 @@ async function prefillFromSource(spawnClaude, labels, source, recorderPatterns =
|
|
|
38211
38666
|
if (source.kind === "file" && !/[.](txt|md|csv|vtt|srt)$/i.test(source.name)) return { ok: false, answers: {}, unknown: labels, held: {}, unverified: [], error: "Use plain text, .vtt or .srt." };
|
|
38212
38667
|
const empty = source.kind === "text" && !source.text.trim() || source.kind === "url" && !source.url.trim() || source.kind === "recorder" && !source.instruction.trim() || source.kind === "file" && !source.path.trim();
|
|
38213
38668
|
if (empty) return { ok: false, answers: {}, unknown: labels, held: {}, unverified: [], error: "nothing to read" };
|
|
38669
|
+
if (source.kind === "url") return { ok: false, answers: {}, unknown: labels, held: {}, unverified: [], error: LINK_READING_OFF_MESSAGE };
|
|
38214
38670
|
if (source.kind === "text" && source.text.length > 6e4) return { ok: false, answers: {}, unknown: labels, held: {}, unverified: [], error: "The limit is 60,000 characters. Paste an excerpt or shorten this text file." };
|
|
38215
38671
|
const result = await runPrefill(() => spawnClaude(prefillPromptForSource(labels, source) + "\nQuestion schema (use only these choices; multiple choices use an array in the cited answer object\u2019s value):\n" + JSON.stringify(questions), prefillTools(source, recorderPatterns)), labels, source.kind === "text" ? source.text : void 0);
|
|
38216
38672
|
result.answers = validatePrefillAnswers(questions, result.answers);
|
|
@@ -38269,7 +38725,7 @@ function validatePrefillAnswers(questions, proposed) {
|
|
|
38269
38725
|
}
|
|
38270
38726
|
return result;
|
|
38271
38727
|
}
|
|
38272
|
-
var INTAKE_FORM_NAME2, INTAKE_CONTACT_TAG, RECORDER_HINTS;
|
|
38728
|
+
var INTAKE_FORM_NAME2, INTAKE_CONTACT_TAG, RECORDER_HINTS, LINK_READING_OFF_MESSAGE;
|
|
38273
38729
|
var init_client_intake = __esm({
|
|
38274
38730
|
"src/client-intake.ts"() {
|
|
38275
38731
|
"use strict";
|
|
@@ -38280,6 +38736,7 @@ var init_client_intake = __esm({
|
|
|
38280
38736
|
INTAKE_FORM_NAME2 = "GHL Command Blueprint \u2014 Client Intake";
|
|
38281
38737
|
INTAKE_CONTACT_TAG = "blueprint-intake";
|
|
38282
38738
|
RECORDER_HINTS = ["plaud", "otter", "fireflies", "grain", "fathom", "tldv", "granola", "zoom", "read.ai", "readai", "supernormal", "avoma"];
|
|
38739
|
+
LINK_READING_OFF_MESSAGE = "Reading a link isn't available in this version. Open the page, copy its text, and paste it into Paste a transcript.";
|
|
38283
38740
|
}
|
|
38284
38741
|
});
|
|
38285
38742
|
|
|
@@ -38343,7 +38800,7 @@ var init_plan_store = __esm({
|
|
|
38343
38800
|
// src/agency-profile-page.ts
|
|
38344
38801
|
function agencyProfilePageHtml(p, clients = [], seatToken = "") {
|
|
38345
38802
|
const offers = (p.offers ?? []).map((o) => `${o.name}${o.priceFloor ? ` - ${o.priceFloor}${o.cadence === "monthly" ? "/mo" : ""}` : ""}`).join("\n");
|
|
38346
|
-
const
|
|
38803
|
+
const os9 = p.agencyOs ?? {};
|
|
38347
38804
|
const lines = (xs) => (xs ?? []).join("\n");
|
|
38348
38805
|
const sops = (p.delivery?.sops ?? []).join("\n");
|
|
38349
38806
|
const chatOn = readAccountAdminSettings().chatCreation;
|
|
@@ -38481,36 +38938,36 @@ ${p.agencyName ? "" : ` <div class="cds-callout warn"><span class="ttl">Add an a
|
|
|
38481
38938
|
<div class="cds-panel-head"><div class="txt"><div class="cds-panel-label">Your offer and method</div><h2 class="cds-panel-title">Who you are, who you serve, what you promise, how you deliver</h2></div></div>
|
|
38482
38939
|
<p>Filled in once, in your own words. Used on the assessment report, in proposals, in every Claude session's opening brief, and by every Command OS build.</p>
|
|
38483
38940
|
<ul class="cds-list">
|
|
38484
|
-
${section2("Your brand: who you are", progress([
|
|
38485
|
-
${area("story", "Your story: why this agency exists, in two or three sentences",
|
|
38486
|
-
${area("method", "Your method: how you get results, and what you call it",
|
|
38941
|
+
${section2("Your brand: who you are", progress([os9.story, os9.method, os9.voice, os9.goals, os9.values, os9.proof]), `
|
|
38942
|
+
${area("story", "Your story: why this agency exists, in two or three sentences", os9.story, { placeholder: "I ran a clinic for twelve years and watched good practices lose patients to voicemail\u2026" })}
|
|
38943
|
+
${area("method", "Your method: how you get results, and what you call it", os9.method, { placeholder: "The Full Room method: one weekend, a full calendar, a system that keeps booking after we leave" })}
|
|
38487
38944
|
<div class="cds-grid">
|
|
38488
|
-
${area("voice", "How you sound: three words, then a sentence",
|
|
38489
|
-
${area("goals", "Your goal for the next twelve months",
|
|
38945
|
+
${area("voice", "How you sound: three words, then a sentence", os9.voice, { placeholder: "Direct, warm, clinical. Specifics over hype." })}
|
|
38946
|
+
${area("goals", "Your goal for the next twelve months", os9.goals, { placeholder: "$50k a month from 51 subscribers" })}
|
|
38490
38947
|
</div>
|
|
38491
|
-
${area("values", "What you stand for and what you refuse to do: one per line", lines(
|
|
38492
|
-
${area("proof", "Proof you may state, with permission: one per line", lines(
|
|
38493
|
-
${section2("Ideal client: who you serve", progress([
|
|
38494
|
-
${area("icaWho", "Who they are: role, business, size, where",
|
|
38948
|
+
${area("values", "What you stand for and what you refuse to do: one per line", lines(os9.values), { placeholder: "Never a false all-clear No cold texting anyone who did not opt in" })}
|
|
38949
|
+
${area("proof", "Proof you may state, with permission: one per line", lines(os9.proof), { placeholder: "Radiance Med Spa: 34 consults booked in one weekend (permission on file)" })}`)}
|
|
38950
|
+
${section2("Ideal client: who you serve", progress([os9.ica?.who, os9.ica?.pains, os9.ica?.desires, os9.ica?.objections, os9.ica?.triggers, os9.ica?.vocabulary, os9.ica?.hangouts, os9.ica?.villains]), `
|
|
38951
|
+
${area("icaWho", "Who they are: role, business, size, where", os9.ica?.who, { placeholder: "The owner of a two-provider med spa in a mid-size US city, doing $40k a month and answering the phone herself" })}
|
|
38495
38952
|
<div class="cds-grid">
|
|
38496
|
-
${area("icaPains", "Their pains, worst first: one per line", lines(
|
|
38497
|
-
${area("icaDesires", "What they want, most first: one per line", lines(
|
|
38498
|
-
${area("icaObjections", "Why they say no: one per line", lines(
|
|
38499
|
-
${area("icaTriggers", "What makes them buy now: one per line", lines(
|
|
38500
|
-
${area("icaVocabulary", "Their own words for the problem: one per line", lines(
|
|
38501
|
-
${area("icaHangouts", "Where they read, listen and gather: one per line", lines(
|
|
38953
|
+
${area("icaPains", "Their pains, worst first: one per line", lines(os9.ica?.pains))}
|
|
38954
|
+
${area("icaDesires", "What they want, most first: one per line", lines(os9.ica?.desires))}
|
|
38955
|
+
${area("icaObjections", "Why they say no: one per line", lines(os9.ica?.objections))}
|
|
38956
|
+
${area("icaTriggers", "What makes them buy now: one per line", lines(os9.ica?.triggers))}
|
|
38957
|
+
${area("icaVocabulary", "Their own words for the problem: one per line", lines(os9.ica?.vocabulary))}
|
|
38958
|
+
${area("icaHangouts", "Where they read, listen and gather: one per line", lines(os9.ica?.hangouts))}
|
|
38502
38959
|
</div>
|
|
38503
|
-
${area("icaVillains", "What they have tried and blame: one per line", lines(
|
|
38504
|
-
${section2("The offer: what you promise", progress([
|
|
38960
|
+
${area("icaVillains", "What they have tried and blame: one per line", lines(os9.ica?.villains), { placeholder: "The last agency that charged $3,000 a month for two posts a week" })}`)}
|
|
38961
|
+
${section2("The offer: what you promise", progress([os9.offer?.name, os9.offer?.price, os9.offer?.promise, os9.offer?.valueStack, os9.offer?.guarantee, os9.offer?.accelerators]), `
|
|
38505
38962
|
<div class="cds-grid">
|
|
38506
|
-
${input("offerName", "The offer's name",
|
|
38507
|
-
${input("offerPrice", "Price, and what it is per",
|
|
38963
|
+
${input("offerName", "The offer's name", os9.offer?.name, { placeholder: "The Full Room" })}
|
|
38964
|
+
${input("offerPrice", "Price, and what it is per", os9.offer?.price, { placeholder: "$5,000 per event plus 30% of revenue" })}
|
|
38508
38965
|
</div>
|
|
38509
|
-
${area("offerPromise", "The promise: one sentence a prospect could repeat",
|
|
38510
|
-
${area("offerStack", "What is in it: one per line", lines(
|
|
38966
|
+
${area("offerPromise", "The promise: one sentence a prospect could repeat", os9.offer?.promise, { placeholder: "A full room in one weekend, and a system that keeps booking after we leave" })}
|
|
38967
|
+
${area("offerStack", "What is in it: one per line", lines(os9.offer?.valueStack), { placeholder: "The campaign page The AI receptionist The 30-day nurture" })}
|
|
38511
38968
|
<div class="cds-grid">
|
|
38512
|
-
${area("offerGuarantee", "The guarantee, exactly as you would say it",
|
|
38513
|
-
${area("offerAccelerators", "Why now: bonuses, deadlines, scarcity, one per line", lines(
|
|
38969
|
+
${area("offerGuarantee", "The guarantee, exactly as you would say it", os9.offer?.guarantee)}
|
|
38970
|
+
${area("offerAccelerators", "Why now: bonuses, deadlines, scarcity, one per line", lines(os9.offer?.accelerators))}
|
|
38514
38971
|
</div>`)}
|
|
38515
38972
|
${section2("How you deliver", progress([p.delivery?.sops, p.staff]), `
|
|
38516
38973
|
${area("sops", "Delivery SOPs: one per line", sops, { placeholder: "Every build gets a rollback journal before go-live" })}
|
|
@@ -40417,7 +40874,7 @@ function writeVerifiedBackup(configPath, originalBytes) {
|
|
|
40417
40874
|
return backupPath;
|
|
40418
40875
|
}
|
|
40419
40876
|
function sha256(bytes) {
|
|
40420
|
-
return (0,
|
|
40877
|
+
return (0, import_node_crypto13.createHash)("sha256").update(bytes).digest("hex");
|
|
40421
40878
|
}
|
|
40422
40879
|
function baselineOf(bytes, stat) {
|
|
40423
40880
|
return { size: stat.size, mtimeMs: stat.mtimeMs, hash: sha256(bytes) };
|
|
@@ -40443,7 +40900,7 @@ function assertNotStale(configPath, baseline) {
|
|
|
40443
40900
|
}
|
|
40444
40901
|
async function atomicReplace(opts) {
|
|
40445
40902
|
const dir = path31.dirname(opts.configPath);
|
|
40446
|
-
const tempPath = path31.join(dir, `.${path31.basename(opts.configPath)}.tmp-${process.pid}-${(0,
|
|
40903
|
+
const tempPath = path31.join(dir, `.${path31.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto13.randomBytes)(4).toString("hex")}`);
|
|
40447
40904
|
const fd = fs33.openSync(tempPath, "w");
|
|
40448
40905
|
try {
|
|
40449
40906
|
fs33.writeFileSync(fd, opts.newBytes);
|
|
@@ -40593,14 +41050,14 @@ async function runInstall(argv, ioOverride) {
|
|
|
40593
41050
|
return EXIT_ABORTED;
|
|
40594
41051
|
}
|
|
40595
41052
|
}
|
|
40596
|
-
var fs33, os6, path31,
|
|
41053
|
+
var fs33, os6, path31, import_node_crypto13, 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;
|
|
40597
41054
|
var init_config_installer = __esm({
|
|
40598
41055
|
"src/config-installer.ts"() {
|
|
40599
41056
|
"use strict";
|
|
40600
41057
|
fs33 = __toESM(require("node:fs"));
|
|
40601
41058
|
os6 = __toESM(require("node:os"));
|
|
40602
41059
|
path31 = __toESM(require("node:path"));
|
|
40603
|
-
|
|
41060
|
+
import_node_crypto13 = require("node:crypto");
|
|
40604
41061
|
import_node_util = require("node:util");
|
|
40605
41062
|
import_json5 = __toESM(require("json5"));
|
|
40606
41063
|
init_npx_cache_check();
|
|
@@ -41295,71 +41752,122 @@ for(const s of document.querySelectorAll("[data-ink]")){const v=s.dataset.ink;if
|
|
|
41295
41752
|
}
|
|
41296
41753
|
});
|
|
41297
41754
|
|
|
41298
|
-
// src/command-os/
|
|
41299
|
-
async function
|
|
41755
|
+
// src/command-os/prospect/guarded-browser.ts
|
|
41756
|
+
async function openBrowser2() {
|
|
41300
41757
|
let chromium;
|
|
41301
41758
|
try {
|
|
41302
41759
|
({ chromium } = await import("playwright-core"));
|
|
41303
41760
|
} catch (e) {
|
|
41304
|
-
return {
|
|
41761
|
+
return { reason: `the browser library could not be loaded (${e.message.split("\n")[0]})` };
|
|
41762
|
+
}
|
|
41763
|
+
let proxy;
|
|
41764
|
+
try {
|
|
41765
|
+
proxy = await startGuardProxy();
|
|
41766
|
+
} catch (e) {
|
|
41767
|
+
return { reason: `the browser's network gatekeeper could not start (${e.message.split("\n")[0]})` };
|
|
41305
41768
|
}
|
|
41306
|
-
const
|
|
41307
|
-
const channels = ["chrome", "msedge"];
|
|
41769
|
+
const args = ["--disable-quic", "--force-webrtc-ip-handling-policy=disable_non_proxied_udp", "--webrtc-ip-handling-policy=disable_non_proxied_udp", "--disable-features=IsolateOrigins,site-per-process", "--disable-site-isolation-trials", "--proxy-bypass-list=<-loopback>", "--no-pings", "--disable-background-networking"];
|
|
41308
41770
|
let last = "";
|
|
41309
|
-
for (const channel of
|
|
41771
|
+
for (const channel of ["chrome", "msedge"]) {
|
|
41310
41772
|
try {
|
|
41311
|
-
|
|
41312
|
-
channel,
|
|
41313
|
-
headless: opts.headless ?? true,
|
|
41314
|
-
viewport: { width: 1440, height: 900 },
|
|
41315
|
-
deviceScaleFactor: 1,
|
|
41316
|
-
args: ["--hide-scrollbars"]
|
|
41317
|
-
});
|
|
41318
|
-
return { ok: true, browser: { context, close: () => context.close().catch(() => void 0) } };
|
|
41773
|
+
return { browser: await chromium.launch({ channel, headless: true, chromiumSandbox: true, args, proxy: { server: proxy.url, bypass: "<-loopback>", username: proxy.username, password: proxy.password } }), proxy };
|
|
41319
41774
|
} catch (e) {
|
|
41320
|
-
last = e.message;
|
|
41775
|
+
last = e.message.split("\n")[0];
|
|
41321
41776
|
}
|
|
41322
41777
|
}
|
|
41323
|
-
|
|
41778
|
+
await proxy.close();
|
|
41779
|
+
return { reason: `Chrome or Edge could not be opened on this computer (${last})` };
|
|
41324
41780
|
}
|
|
41325
|
-
async function
|
|
41326
|
-
const
|
|
41327
|
-
return
|
|
41781
|
+
async function openGuardedBrowser(viewport = { width: 1440, height: 900 }) {
|
|
41782
|
+
const opened = await openBrowser2().catch((e) => ({ reason: e.message }));
|
|
41783
|
+
if ("reason" in opened) return { ok: false, reason: opened.reason };
|
|
41784
|
+
const { browser, proxy } = opened;
|
|
41785
|
+
try {
|
|
41786
|
+
const context = await browser.newContext({ viewport, serviceWorkers: "block", acceptDownloads: false, proxy: { server: proxy.url, username: proxy.username, password: proxy.password } });
|
|
41787
|
+
await context.route("**/*", async (route) => {
|
|
41788
|
+
const m = route.request().method();
|
|
41789
|
+
if (m !== "GET" && m !== "HEAD") {
|
|
41790
|
+
await route.abort("blockedbyclient").catch(() => void 0);
|
|
41791
|
+
return;
|
|
41792
|
+
}
|
|
41793
|
+
await route.continue().catch(() => void 0);
|
|
41794
|
+
});
|
|
41795
|
+
await context.routeWebSocket(/.*/, (ws) => ws.close({ code: 1008, reason: "read-only" }));
|
|
41796
|
+
context.on("page", (p) => {
|
|
41797
|
+
if (context.pages().length > 1) void p.close().catch(() => void 0);
|
|
41798
|
+
});
|
|
41799
|
+
return { ok: true, context, close: () => closeQuietly(browser, proxy) };
|
|
41800
|
+
} catch (e) {
|
|
41801
|
+
await closeQuietly(browser, proxy);
|
|
41802
|
+
return { ok: false, reason: e.message.split("\n")[0] };
|
|
41803
|
+
}
|
|
41328
41804
|
}
|
|
41329
|
-
|
|
41330
|
-
|
|
41805
|
+
async function closeQuietly(browser, proxy) {
|
|
41806
|
+
await Promise.race([
|
|
41807
|
+
(async () => {
|
|
41808
|
+
await browser.close().catch(() => void 0);
|
|
41809
|
+
await proxy.close().catch(() => void 0);
|
|
41810
|
+
})(),
|
|
41811
|
+
new Promise((r) => setTimeout(r, 1e4))
|
|
41812
|
+
]);
|
|
41813
|
+
void proxy.close().catch(() => void 0);
|
|
41814
|
+
}
|
|
41815
|
+
var init_guarded_browser = __esm({
|
|
41816
|
+
"src/command-os/prospect/guarded-browser.ts"() {
|
|
41331
41817
|
"use strict";
|
|
41332
|
-
|
|
41818
|
+
init_net_guard();
|
|
41333
41819
|
}
|
|
41334
41820
|
});
|
|
41335
41821
|
|
|
41336
41822
|
// src/command-os/design/extract-browser.ts
|
|
41337
41823
|
async function browserPass(url, opts = {}) {
|
|
41338
|
-
const
|
|
41339
|
-
|
|
41340
|
-
|
|
41341
|
-
|
|
41342
|
-
|
|
41824
|
+
const navMs = opts.timeoutMs ?? 3e4;
|
|
41825
|
+
const deadlineMs = navMs * 2 + 2e4;
|
|
41826
|
+
let timer;
|
|
41827
|
+
let expiredAt = false;
|
|
41828
|
+
let closeOnce;
|
|
41829
|
+
const expired = new Promise((resolve13) => {
|
|
41830
|
+
timer = setTimeout(() => {
|
|
41831
|
+
expiredAt = true;
|
|
41832
|
+
resolve13({ ok: false, reason: `The rendered pass took longer than ${Math.round(deadlineMs / 1e3)} seconds and was stopped.` });
|
|
41833
|
+
}, deadlineMs);
|
|
41834
|
+
});
|
|
41835
|
+
const work = (async () => {
|
|
41836
|
+
const problem = await publicAddressProblem(url);
|
|
41837
|
+
if (problem && problem !== "the address does not resolve") return { ok: false, reason: `The rendered pass did not open ${url}: ${problem}.` };
|
|
41838
|
+
const launched = await openGuardedBrowser({ width: 1440, height: 900 });
|
|
41839
|
+
if (!launched.ok) return { ok: false, reason: `The rendered pass could not start a guarded browser: ${launched.reason}` };
|
|
41840
|
+
let closing;
|
|
41841
|
+
closeOnce = () => closing ??= launched.close();
|
|
41842
|
+
if (expiredAt) {
|
|
41843
|
+
await closeOnce();
|
|
41844
|
+
return { ok: false, reason: "The rendered pass was stopped at its deadline." };
|
|
41845
|
+
}
|
|
41846
|
+
const page = await launched.context.newPage();
|
|
41343
41847
|
await page.setViewportSize({ width: 1440, height: 900 });
|
|
41344
|
-
await page.goto(url, { waitUntil: "networkidle", timeout:
|
|
41345
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout:
|
|
41848
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: navMs }).catch(async () => {
|
|
41849
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: navMs });
|
|
41346
41850
|
});
|
|
41347
41851
|
await page.waitForTimeout(800);
|
|
41348
41852
|
const facts = await page.evaluate(BROWSER_FACTS_JS);
|
|
41349
41853
|
const out = { ...facts, logoSvg: facts.logoSvg ?? void 0, logoSrc: facts.logoSrc ?? void 0 };
|
|
41350
41854
|
if (opts.screenshot !== false) out.screenshotPng = await page.screenshot({ type: "png", fullPage: false });
|
|
41351
41855
|
return { ok: true, facts: out };
|
|
41352
|
-
}
|
|
41353
|
-
|
|
41856
|
+
})().catch((e) => ({ ok: false, reason: `The rendered pass failed: ${e.message.split("\n")[0]}` }));
|
|
41857
|
+
try {
|
|
41858
|
+
return await Promise.race([work, expired]);
|
|
41354
41859
|
} finally {
|
|
41355
|
-
|
|
41860
|
+
clearTimeout(timer);
|
|
41861
|
+
expiredAt = true;
|
|
41862
|
+
if (closeOnce) await closeOnce();
|
|
41356
41863
|
}
|
|
41357
41864
|
}
|
|
41358
41865
|
var BROWSER_FACTS_JS;
|
|
41359
41866
|
var init_extract_browser = __esm({
|
|
41360
41867
|
"src/command-os/design/extract-browser.ts"() {
|
|
41361
41868
|
"use strict";
|
|
41362
|
-
|
|
41869
|
+
init_guarded_browser();
|
|
41870
|
+
init_net_guard();
|
|
41363
41871
|
BROWSER_FACTS_JS = `(() => {
|
|
41364
41872
|
const cs = (el) => el ? getComputedStyle(el) : null;
|
|
41365
41873
|
const rgbOf = (v) => (v && v !== "rgba(0, 0, 0, 0)" && v !== "transparent") ? v : null;
|
|
@@ -42855,7 +43363,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
42855
43363
|
const dir = args.outDir ?? (0, import_node_path8.join)((0, import_node_os.homedir)(), "Documents", "assessments");
|
|
42856
43364
|
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
42857
43365
|
const slug3 = (state.biz || "assessment").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
42858
|
-
const file2 = (0, import_node_path8.join)(dir, `${slug3}-${(0,
|
|
43366
|
+
const file2 = (0, import_node_path8.join)(dir, `${slug3}-${(0, import_node_crypto14.randomBytes)(4).toString("hex")}.html`);
|
|
42859
43367
|
(0, import_node_fs8.writeFileSync)(file2, html2, "utf8");
|
|
42860
43368
|
return {
|
|
42861
43369
|
written: file2,
|
|
@@ -42878,7 +43386,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
42878
43386
|
}
|
|
42879
43387
|
);
|
|
42880
43388
|
}
|
|
42881
|
-
var import_node_util3, import_zod61,
|
|
43389
|
+
var import_node_util3, import_zod61, import_node_crypto14, import_node_fs8, import_node_os, import_node_path8, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema;
|
|
42882
43390
|
var init_assessment = __esm({
|
|
42883
43391
|
"src/tools/assessment.ts"() {
|
|
42884
43392
|
"use strict";
|
|
@@ -42891,7 +43399,7 @@ var init_assessment = __esm({
|
|
|
42891
43399
|
init_store3();
|
|
42892
43400
|
init_engine();
|
|
42893
43401
|
init_report();
|
|
42894
|
-
|
|
43402
|
+
import_node_crypto14 = require("node:crypto");
|
|
42895
43403
|
import_node_fs8 = require("node:fs");
|
|
42896
43404
|
import_node_os = require("node:os");
|
|
42897
43405
|
import_node_path8 = require("node:path");
|
|
@@ -43439,7 +43947,7 @@ function notify(message) {
|
|
|
43439
43947
|
}
|
|
43440
43948
|
}
|
|
43441
43949
|
}
|
|
43442
|
-
function
|
|
43950
|
+
function openBrowser3(port) {
|
|
43443
43951
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
43444
43952
|
const openArgs = process.platform === "win32" ? ["/c", "start", "", `http://localhost:${port}`] : [`http://localhost:${port}`];
|
|
43445
43953
|
try {
|
|
@@ -43952,8 +44460,9 @@ function intakeWorkspace(id,name){const d=iData[id];const box=document.createEle
|
|
|
43952
44460
|
note.textContent="Command OS cannot verify this account\u2019s sender. This button records a manual task to confirm the sender and send the questionnaire in GoHighLevel.";w.appendChild(note);box.appendChild(w)}
|
|
43953
44461
|
if(mode==="transcript"){const w=document.createElement("div");w.className="cds-stack";
|
|
43954
44462
|
const tabs=document.createElement("div");tabs.className="cds-tabs ichoice";tabs.setAttribute("role","group");tabs.setAttribute("aria-label","Where the material comes from");
|
|
43955
|
-
|
|
43956
|
-
|
|
44463
|
+
/* "From a link" is off in 3.88.3 (the browser/fetch inventory, 2026-10-09): the tab is gone and a remembered choice falls back to Paste. */
|
|
44464
|
+
if(!iSub[id]||iSub[id]==="url")iSub[id]="paste";
|
|
44465
|
+
for(const [k,lab] of [["paste","Paste a transcript"],["doc","A text document"],["recorder","From my recorder"]]){
|
|
43957
44466
|
const tb=document.createElement("button");tb.className="cds-tab";tb.textContent=lab;if(iSub[id]===k){tb.className="cds-tab on";tb.setAttribute("aria-current","page")}
|
|
43958
44467
|
tb.onclick=()=>{iSub[id]=k;render(lastState,lastRun)};tabs.appendChild(tb)}
|
|
43959
44468
|
w.appendChild(tabs);
|
|
@@ -45111,7 +45620,7 @@ function cockpitHeader(req, name) {
|
|
|
45111
45620
|
return Array.isArray(value) ? value.join(",") : value ?? "";
|
|
45112
45621
|
}
|
|
45113
45622
|
function headerFields(req, name) {
|
|
45114
|
-
if (req instanceof
|
|
45623
|
+
if (req instanceof http2.IncomingMessage || "rawHeaders" in req) {
|
|
45115
45624
|
const raw = req.rawHeaders;
|
|
45116
45625
|
if (!Array.isArray(raw)) return [];
|
|
45117
45626
|
const values = [];
|
|
@@ -45308,7 +45817,7 @@ async function runDashboard(argv) {
|
|
|
45308
45817
|
return 0;
|
|
45309
45818
|
}
|
|
45310
45819
|
if (await cockpitAlreadyRunning(port)) {
|
|
45311
|
-
|
|
45820
|
+
openBrowser3(port);
|
|
45312
45821
|
process.stderr.write(`
|
|
45313
45822
|
Command OS is already running. Opening localhost:${port}
|
|
45314
45823
|
|
|
@@ -45652,7 +46161,7 @@ async function runDashboard(argv) {
|
|
|
45652
46161
|
};
|
|
45653
46162
|
const connectionsRoutes = createConnectionsRoutes(seatToken, port, gate2.plan);
|
|
45654
46163
|
const briefRoutes = createBriefRoutes(seatToken, port, gate2.plan, registry2);
|
|
45655
|
-
const server2 =
|
|
46164
|
+
const server2 = http2.createServer(guarded(seatToken, port, (req, res) => {
|
|
45656
46165
|
const url = cockpitPath(req);
|
|
45657
46166
|
if (connectionsRoutes(req, res)) return;
|
|
45658
46167
|
if (briefRoutes(req, res)) return;
|
|
@@ -46224,6 +46733,11 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
46224
46733
|
return;
|
|
46225
46734
|
}
|
|
46226
46735
|
if (url === "/api/intake-prefill") {
|
|
46736
|
+
if (args.url && !args.file) {
|
|
46737
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
46738
|
+
res.end(JSON.stringify({ ok: false, answers: {}, unknown: args.labels ?? [], error: LINK_READING_OFF_MESSAGE }));
|
|
46739
|
+
return;
|
|
46740
|
+
}
|
|
46227
46741
|
let bin;
|
|
46228
46742
|
try {
|
|
46229
46743
|
bin = claudeBin();
|
|
@@ -46529,7 +47043,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
46529
47043
|
const address = new URL(String(args.url || ""));
|
|
46530
47044
|
if (!["http:", "https:"].includes(address.protocol)) throw new Error("Enter the public website address.");
|
|
46531
47045
|
const before = readStatusRecord(locationId2);
|
|
46532
|
-
const response = await
|
|
47046
|
+
const response = await guardedFetchLike(address.href, { signal: AbortSignal.timeout(15e3) });
|
|
46533
47047
|
const html2 = await response.text();
|
|
46534
47048
|
const now = readStatusRecord(locationId2);
|
|
46535
47049
|
if (now.preparedStamp !== before.preparedStamp || !response.ok || !verifyPublicationContent(html2, before.preparedStamp || "", before.preparedSentences || [])) throw new Error("Prepared, not yet published. This address does not show the latest handoff. Publish the prepared page, then verify it again.");
|
|
@@ -46561,7 +47075,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
46561
47075
|
if (stale.length) throw new Error(stale[0].reason);
|
|
46562
47076
|
const publication = readStatusRecord(locationId2);
|
|
46563
47077
|
if (!publication.lastPublishedAt || publication.lastError || !publication.lastUrl) throw new Error("The handoff is not yet published. Publish it and verify the public address before sending.");
|
|
46564
|
-
const response = await
|
|
47078
|
+
const response = await guardedFetchLike(publication.lastUrl, { signal: AbortSignal.timeout(15e3) });
|
|
46565
47079
|
const html2 = await response.text();
|
|
46566
47080
|
const snapshot = readHandoffSnapshot(locationId2);
|
|
46567
47081
|
if (!response.ok || !snapshot?.lines.length || !verifyPublicationContent(html2, publication.preparedStamp || "", publication.preparedSentences || [])) throw new Error("The public address does not show the current handoff. Publish the latest page before sending.");
|
|
@@ -47080,7 +47594,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
47080
47594
|
${roster.length} client${roster.length === 1 ? "" : "s"} on the board. Ctrl-C to stop.
|
|
47081
47595
|
|
|
47082
47596
|
`);
|
|
47083
|
-
if (!argv.includes("--no-open"))
|
|
47597
|
+
if (!argv.includes("--no-open")) openBrowser3(port);
|
|
47084
47598
|
{
|
|
47085
47599
|
const { state: fixed, recovered } = recoverStuckStages(readCockpitState());
|
|
47086
47600
|
if (recovered) {
|
|
@@ -47099,7 +47613,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
47099
47613
|
process.on("SIGTERM", stop2);
|
|
47100
47614
|
});
|
|
47101
47615
|
}
|
|
47102
|
-
var import_zod62, import_node_util4, fs37, path35,
|
|
47616
|
+
var import_zod62, import_node_util4, fs37, path35, http2, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, SCREEN_HEADS, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, ConnectionRequest, DisconnectRequest;
|
|
47103
47617
|
var init_dashboard = __esm({
|
|
47104
47618
|
"src/dashboard.ts"() {
|
|
47105
47619
|
"use strict";
|
|
@@ -47138,7 +47652,7 @@ var init_dashboard = __esm({
|
|
|
47138
47652
|
init_build_obligations();
|
|
47139
47653
|
fs37 = __toESM(require("fs"));
|
|
47140
47654
|
path35 = __toESM(require("path"));
|
|
47141
|
-
|
|
47655
|
+
http2 = __toESM(require("http"));
|
|
47142
47656
|
import_child_process4 = require("child_process");
|
|
47143
47657
|
init_credentials_store();
|
|
47144
47658
|
init_attestation();
|
|
@@ -47200,6 +47714,7 @@ var init_dashboard = __esm({
|
|
|
47200
47714
|
osmod = __toESM(require("os"));
|
|
47201
47715
|
init_script_json();
|
|
47202
47716
|
init_svg_sanitize();
|
|
47717
|
+
init_net_guard();
|
|
47203
47718
|
STAGES2 = [
|
|
47204
47719
|
"Intake",
|
|
47205
47720
|
"Brief",
|
|
@@ -49006,13 +49521,13 @@ function contactServiceNote(payload) {
|
|
|
49006
49521
|
function registerSmartListTools(server2, builderClient) {
|
|
49007
49522
|
if (!builderClient) return;
|
|
49008
49523
|
const client = builderClient;
|
|
49009
|
-
async function smartListRequest(method,
|
|
49010
|
-
const url = `${SMARTLIST_BASE}${
|
|
49524
|
+
async function smartListRequest(method, path45, body) {
|
|
49525
|
+
const url = `${SMARTLIST_BASE}${path45}`;
|
|
49011
49526
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
49012
49527
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
49013
49528
|
if (!response.ok) {
|
|
49014
49529
|
const text3 = await response.text();
|
|
49015
|
-
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${
|
|
49530
|
+
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path45}
|
|
49016
49531
|
${text3}`);
|
|
49017
49532
|
}
|
|
49018
49533
|
const text2 = await response.text();
|
|
@@ -49257,11 +49772,11 @@ var init_smart_lists = __esm({
|
|
|
49257
49772
|
function registerReputationTools(server2, builderClient) {
|
|
49258
49773
|
if (!builderClient) return;
|
|
49259
49774
|
const client = builderClient;
|
|
49260
|
-
async function reputationRequest(method,
|
|
49261
|
-
const response = await builderRequest(client, method, `${REPUTATION_BASE}${
|
|
49775
|
+
async function reputationRequest(method, path45) {
|
|
49776
|
+
const response = await builderRequest(client, method, `${REPUTATION_BASE}${path45}`);
|
|
49262
49777
|
if (!response.ok) {
|
|
49263
49778
|
const text3 = await response.text();
|
|
49264
|
-
throw new Error(`Reputation API Error ${response.status}: ${method} ${
|
|
49779
|
+
throw new Error(`Reputation API Error ${response.status}: ${method} ${path45}
|
|
49265
49780
|
${text3}`);
|
|
49266
49781
|
}
|
|
49267
49782
|
const text2 = await response.text();
|
|
@@ -49399,11 +49914,11 @@ var init_email_campaigns = __esm({
|
|
|
49399
49914
|
function registerMembershipTools(server2, builderClient) {
|
|
49400
49915
|
if (!builderClient) return;
|
|
49401
49916
|
const client = builderClient;
|
|
49402
|
-
async function membershipRequest(
|
|
49403
|
-
const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${
|
|
49917
|
+
async function membershipRequest(path45, method = "GET", body) {
|
|
49918
|
+
const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path45}`, body ? { body } : {});
|
|
49404
49919
|
if (!response.ok) {
|
|
49405
49920
|
const text3 = await response.text();
|
|
49406
|
-
throw new Error(`Membership API Error ${response.status}: ${method} ${
|
|
49921
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path45}
|
|
49407
49922
|
${text3}`);
|
|
49408
49923
|
}
|
|
49409
49924
|
const text2 = await response.text();
|
|
@@ -50422,10 +50937,10 @@ async function confirm(run) {
|
|
|
50422
50937
|
async function runProbes(client, loc) {
|
|
50423
50938
|
return Promise.all(
|
|
50424
50939
|
PROBES.map((probe) => confirm(async () => {
|
|
50425
|
-
const
|
|
50940
|
+
const path45 = probe.path.replace("{loc}", loc);
|
|
50426
50941
|
const params = probe.params ? Object.fromEntries(Object.entries(probe.params).map(([k, v]) => [k, substitute(v, loc)])) : void 0;
|
|
50427
50942
|
try {
|
|
50428
|
-
const raw = await client.get(
|
|
50943
|
+
const raw = await client.get(path45, { params, noRetry: true });
|
|
50429
50944
|
const count2 = countRows(raw, probe.listKeys);
|
|
50430
50945
|
return {
|
|
50431
50946
|
key: probe.key,
|
|
@@ -51243,6 +51758,161 @@ var init_probe = __esm({
|
|
|
51243
51758
|
}
|
|
51244
51759
|
});
|
|
51245
51760
|
|
|
51761
|
+
// src/command-os/design/browser.ts
|
|
51762
|
+
async function evaluateSource(page, source, arg) {
|
|
51763
|
+
const fn = new Function("arg", `return (${source})(arg);`);
|
|
51764
|
+
return await page.evaluate(fn, arg);
|
|
51765
|
+
}
|
|
51766
|
+
var init_browser = __esm({
|
|
51767
|
+
"src/command-os/design/browser.ts"() {
|
|
51768
|
+
"use strict";
|
|
51769
|
+
init_interactive_capture();
|
|
51770
|
+
}
|
|
51771
|
+
});
|
|
51772
|
+
|
|
51773
|
+
// src/command-os/design/local-target.ts
|
|
51774
|
+
function realDir(p) {
|
|
51775
|
+
if (!p || !p.trim()) return null;
|
|
51776
|
+
try {
|
|
51777
|
+
const r = fs40.realpathSync(path38.resolve(p));
|
|
51778
|
+
return fs40.statSync(r).isDirectory() ? r : null;
|
|
51779
|
+
} catch {
|
|
51780
|
+
return null;
|
|
51781
|
+
}
|
|
51782
|
+
}
|
|
51783
|
+
function within(root, p) {
|
|
51784
|
+
return p === root || p.startsWith(root.endsWith(path38.sep) ? root : root + path38.sep);
|
|
51785
|
+
}
|
|
51786
|
+
function safeDesignRoot(cwd) {
|
|
51787
|
+
const r = realDir(cwd);
|
|
51788
|
+
if (!r) return null;
|
|
51789
|
+
const home = realDir(os7.homedir());
|
|
51790
|
+
if (r === path38.parse(r).root) return null;
|
|
51791
|
+
if (home && (r === home || within(r, home))) return null;
|
|
51792
|
+
const depth = (p) => path38.relative(path38.parse(p).root, p).split(path38.sep).filter(Boolean).length;
|
|
51793
|
+
if ([path38.resolve(cwd), r].some((p) => depth(p) < 2)) return null;
|
|
51794
|
+
if (SYSTEM_DIRS.some((d) => within(d, r))) return null;
|
|
51795
|
+
if (windowsSystemDirs().some((d) => within(d, r))) return null;
|
|
51796
|
+
const own = home ? ["Documents", "Downloads", "Desktop", "Movies", "Music", "Pictures", "Public", "OneDrive"].map((n) => path38.join(home, n)) : [];
|
|
51797
|
+
const shared = ["/Users/Shared", "/tmp", "/private/tmp", "/var/tmp", "/private/var/tmp", "/var/folders", "/private/var/folders", realDir(os7.tmpdir()) ?? os7.tmpdir()];
|
|
51798
|
+
if ([...own, ...shared].some((d) => r === d || r === realDir(d))) return null;
|
|
51799
|
+
const appData = home ? [path38.join(home, "Library"), path38.join(home, "AppData"), process.env.APPDATA, process.env.LOCALAPPDATA] : [];
|
|
51800
|
+
for (const d of appData) if (d && (within(realDir(d) ?? path38.resolve(d), r) || within(path38.resolve(d), r))) return null;
|
|
51801
|
+
if (home && within(home, r) && path38.relative(home, r).split(path38.sep)[0].startsWith(".")) return null;
|
|
51802
|
+
return r;
|
|
51803
|
+
}
|
|
51804
|
+
function windowsSystemDirs() {
|
|
51805
|
+
if (process.platform !== "win32") return [];
|
|
51806
|
+
return [process.env.SystemRoot, process.env.ProgramFiles, process.env["ProgramFiles(x86)"], process.env.ProgramData].filter((d) => !!d).map((d) => realDir(d) ?? path38.resolve(d));
|
|
51807
|
+
}
|
|
51808
|
+
function designRoots(env2 = process.env, cwd = process.cwd()) {
|
|
51809
|
+
const out = [];
|
|
51810
|
+
const named = env2[DESIGN_DIR_ENV]?.trim() ? safeDesignRoot(env2[DESIGN_DIR_ENV]) : null;
|
|
51811
|
+
const moved = env2[BRANDS_ENV]?.trim();
|
|
51812
|
+
const brands = moved ? safeDesignRoot(moved) : realDir(brandsDir());
|
|
51813
|
+
for (const r of [named, safeDesignRoot(cwd), brands]) if (r && !out.includes(r)) out.push(r);
|
|
51814
|
+
return out;
|
|
51815
|
+
}
|
|
51816
|
+
function urlFor(root, file2) {
|
|
51817
|
+
return LOCAL_ORIGIN + "/" + path38.relative(root, file2).split(path38.sep).map(encodeURIComponent).join("/");
|
|
51818
|
+
}
|
|
51819
|
+
function hidden(rel) {
|
|
51820
|
+
return rel.split(path38.sep).some((s) => s.startsWith("."));
|
|
51821
|
+
}
|
|
51822
|
+
function checkLocalTarget(target, roots = designRoots()) {
|
|
51823
|
+
const abs = path38.resolve(target);
|
|
51824
|
+
let file2;
|
|
51825
|
+
try {
|
|
51826
|
+
file2 = fs40.realpathSync(abs);
|
|
51827
|
+
} catch {
|
|
51828
|
+
throw new Error(`No file at ${abs}.`);
|
|
51829
|
+
}
|
|
51830
|
+
const ext = path38.extname(file2).toLowerCase();
|
|
51831
|
+
if (ext !== ".html" && ext !== ".htm") throw new Error(`verify_design renders .html files only; ${abs} is not one.`);
|
|
51832
|
+
const st = fs40.statSync(file2);
|
|
51833
|
+
if (!st.isFile()) throw new Error(`${abs} is not a file.`);
|
|
51834
|
+
if (st.size > MAX_PAGE_BYTES) throw new Error(`${abs} is larger than 10 MB, so it is not rendered.`);
|
|
51835
|
+
const root = roots.find((r) => within(r, file2) && !hidden(path38.relative(r, file2)));
|
|
51836
|
+
if (!root) {
|
|
51837
|
+
const where = roots.length ? roots.join(", ") : "none is set up on this computer";
|
|
51838
|
+
throw new Error(`verify_design only renders pages inside the design folders (${where}); ${abs} is outside them. Move the page into your project or brand folder, or set ${DESIGN_DIR_ENV} to the folder that holds it.`);
|
|
51839
|
+
}
|
|
51840
|
+
return { file: file2, root, url: urlFor(root, file2), html: fs40.readFileSync(file2, "utf8") };
|
|
51841
|
+
}
|
|
51842
|
+
function localFileFor(root, raw) {
|
|
51843
|
+
let u;
|
|
51844
|
+
try {
|
|
51845
|
+
u = new URL(raw);
|
|
51846
|
+
} catch {
|
|
51847
|
+
return null;
|
|
51848
|
+
}
|
|
51849
|
+
if (u.origin !== LOCAL_ORIGIN || u.username || u.password) return null;
|
|
51850
|
+
const segs = [];
|
|
51851
|
+
for (const part of u.pathname.split("/").slice(1)) {
|
|
51852
|
+
let s;
|
|
51853
|
+
try {
|
|
51854
|
+
s = decodeURIComponent(part);
|
|
51855
|
+
} catch {
|
|
51856
|
+
return null;
|
|
51857
|
+
}
|
|
51858
|
+
if (!s || s.startsWith(".") || /[\\/\0]/.test(s) || s.includes(":")) return null;
|
|
51859
|
+
segs.push(s);
|
|
51860
|
+
}
|
|
51861
|
+
if (!segs.length) return null;
|
|
51862
|
+
let file2;
|
|
51863
|
+
try {
|
|
51864
|
+
file2 = fs40.realpathSync(path38.join(root, ...segs));
|
|
51865
|
+
} catch {
|
|
51866
|
+
return null;
|
|
51867
|
+
}
|
|
51868
|
+
if (!within(root, file2) || file2 === root || hidden(path38.relative(root, file2))) return null;
|
|
51869
|
+
const contentType = SERVED[path38.extname(file2).toLowerCase()];
|
|
51870
|
+
if (!contentType) return null;
|
|
51871
|
+
try {
|
|
51872
|
+
const st = fs40.statSync(file2);
|
|
51873
|
+
if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
|
|
51874
|
+
} catch {
|
|
51875
|
+
return null;
|
|
51876
|
+
}
|
|
51877
|
+
return { file: file2, contentType };
|
|
51878
|
+
}
|
|
51879
|
+
var fs40, os7, path38, DESIGN_DIR_ENV, LOCAL_ORIGIN, MAX_PAGE_BYTES, MAX_FILE_BYTES, SERVED, SYSTEM_DIRS;
|
|
51880
|
+
var init_local_target = __esm({
|
|
51881
|
+
"src/command-os/design/local-target.ts"() {
|
|
51882
|
+
"use strict";
|
|
51883
|
+
fs40 = __toESM(require("node:fs"));
|
|
51884
|
+
os7 = __toESM(require("node:os"));
|
|
51885
|
+
path38 = __toESM(require("node:path"));
|
|
51886
|
+
init_store();
|
|
51887
|
+
DESIGN_DIR_ENV = "GHL_MCP_DESIGN_DIR";
|
|
51888
|
+
LOCAL_ORIGIN = "http://verify.invalid";
|
|
51889
|
+
MAX_PAGE_BYTES = 10 * 1024 * 1024;
|
|
51890
|
+
MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
51891
|
+
SERVED = {
|
|
51892
|
+
".html": "text/html; charset=utf-8",
|
|
51893
|
+
".htm": "text/html; charset=utf-8",
|
|
51894
|
+
".css": "text/css; charset=utf-8",
|
|
51895
|
+
".js": "text/javascript; charset=utf-8",
|
|
51896
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
51897
|
+
".png": "image/png",
|
|
51898
|
+
".jpg": "image/jpeg",
|
|
51899
|
+
".jpeg": "image/jpeg",
|
|
51900
|
+
".gif": "image/gif",
|
|
51901
|
+
".webp": "image/webp",
|
|
51902
|
+
".avif": "image/avif",
|
|
51903
|
+
".svg": "image/svg+xml",
|
|
51904
|
+
".ico": "image/x-icon",
|
|
51905
|
+
".woff": "font/woff",
|
|
51906
|
+
".woff2": "font/woff2",
|
|
51907
|
+
".ttf": "font/ttf",
|
|
51908
|
+
".otf": "font/otf",
|
|
51909
|
+
".mp4": "video/mp4",
|
|
51910
|
+
".webm": "video/webm"
|
|
51911
|
+
};
|
|
51912
|
+
SYSTEM_DIRS = ["/etc", "/private/etc", "/usr", "/bin", "/sbin", "/System", "/Library", "/opt", "/dev", "/proc", "/sys"];
|
|
51913
|
+
}
|
|
51914
|
+
});
|
|
51915
|
+
|
|
51246
51916
|
// src/command-os/design/png.ts
|
|
51247
51917
|
function decodePng(buf) {
|
|
51248
51918
|
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
@@ -51272,7 +51942,7 @@ function decodePng(buf) {
|
|
|
51272
51942
|
if (interlace !== 0) throw new Error("interlaced PNG is not supported");
|
|
51273
51943
|
const channels = colorType === 0 ? 1 : colorType === 2 ? 3 : colorType === 3 ? 1 : colorType === 4 ? 2 : colorType === 6 ? 4 : 0;
|
|
51274
51944
|
if (!channels) throw new Error(`PNG colour type ${colorType} is not supported`);
|
|
51275
|
-
const raw = (0,
|
|
51945
|
+
const raw = (0, import_node_zlib2.inflateSync)(Buffer.concat(idat));
|
|
51276
51946
|
const bpp = channels;
|
|
51277
51947
|
const stride = width * bpp;
|
|
51278
51948
|
const out = new Uint8Array(width * height * 4);
|
|
@@ -51357,11 +52027,11 @@ function pixelAt(png, x, y) {
|
|
|
51357
52027
|
const o = (yi * png.width + xi) * 4;
|
|
51358
52028
|
return { r: png.rgba[o], g: png.rgba[o + 1], b: png.rgba[o + 2] };
|
|
51359
52029
|
}
|
|
51360
|
-
var
|
|
52030
|
+
var import_node_zlib2, SIG;
|
|
51361
52031
|
var init_png = __esm({
|
|
51362
52032
|
"src/command-os/design/png.ts"() {
|
|
51363
52033
|
"use strict";
|
|
51364
|
-
|
|
52034
|
+
import_node_zlib2 = require("node:zlib");
|
|
51365
52035
|
SIG = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
51366
52036
|
}
|
|
51367
52037
|
});
|
|
@@ -52990,11 +53660,14 @@ async function settle(page) {
|
|
|
52990
53660
|
await page.evaluate(() => document.fonts?.ready).catch(() => void 0);
|
|
52991
53661
|
await page.waitForTimeout(250);
|
|
52992
53662
|
}
|
|
52993
|
-
async function readTarget(target) {
|
|
52994
|
-
if (isUrl(target))
|
|
52995
|
-
|
|
52996
|
-
|
|
52997
|
-
|
|
53663
|
+
async function readTarget(target, roots) {
|
|
53664
|
+
if (isUrl(target)) {
|
|
53665
|
+
const problem = await publicAddressProblem(target);
|
|
53666
|
+
if (problem) throw new Error(`verify_design did not open ${target}: ${problem}.`);
|
|
53667
|
+
return { html: null, url: target, root: null };
|
|
53668
|
+
}
|
|
53669
|
+
const t = checkLocalTarget(target, roots);
|
|
53670
|
+
return { html: t.html, url: t.url, root: t.root };
|
|
52998
53671
|
}
|
|
52999
53672
|
function slidePixels(index, png, file2, sec, brand) {
|
|
53000
53673
|
let img;
|
|
@@ -53038,11 +53711,35 @@ function slidePixels(index, png, file2, sec, brand) {
|
|
|
53038
53711
|
});
|
|
53039
53712
|
return { index, screenshot: file2, bgShare, accentShare, hueBuckets, overImage };
|
|
53040
53713
|
}
|
|
53714
|
+
async function serveLocalPage(context, root, url, html2) {
|
|
53715
|
+
const notServed = { status: 404, contentType: "text/plain", body: "not served" };
|
|
53716
|
+
await context.route(`${LOCAL_ORIGIN}/**`, async (route) => {
|
|
53717
|
+
const req = route.request();
|
|
53718
|
+
if (req.method() !== "GET" && req.method() !== "HEAD") {
|
|
53719
|
+
await route.abort("blockedbyclient").catch(() => void 0);
|
|
53720
|
+
return;
|
|
53721
|
+
}
|
|
53722
|
+
if (req.url() === url) {
|
|
53723
|
+
await route.fulfill({ status: 200, contentType: "text/html; charset=utf-8", body: html2 }).catch(() => void 0);
|
|
53724
|
+
return;
|
|
53725
|
+
}
|
|
53726
|
+
const hit = localFileFor(root, req.url());
|
|
53727
|
+
let body = null;
|
|
53728
|
+
if (hit) {
|
|
53729
|
+
try {
|
|
53730
|
+
body = fs41.readFileSync(hit.file);
|
|
53731
|
+
} catch {
|
|
53732
|
+
body = null;
|
|
53733
|
+
}
|
|
53734
|
+
}
|
|
53735
|
+
await route.fulfill(hit && body ? { status: 200, contentType: hit.contentType, body } : notServed).catch(() => void 0);
|
|
53736
|
+
});
|
|
53737
|
+
}
|
|
53041
53738
|
async function verifyDesign(opts) {
|
|
53042
|
-
const { html: html2, url } = await readTarget(opts.target);
|
|
53739
|
+
const { html: html2, url, root } = await readTarget(opts.target, opts.roots);
|
|
53043
53740
|
const placement = opts.placement ?? "html";
|
|
53044
53741
|
const medium = opts.medium;
|
|
53045
|
-
|
|
53742
|
+
fs41.mkdirSync(opts.outDir, { recursive: true });
|
|
53046
53743
|
const files = [];
|
|
53047
53744
|
const screenshots = [];
|
|
53048
53745
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -53064,10 +53761,16 @@ async function verifyDesign(opts) {
|
|
|
53064
53761
|
if (!conformance.ok) {
|
|
53065
53762
|
rules = [];
|
|
53066
53763
|
} else {
|
|
53067
|
-
const launched = opts.noBrowser ? { ok: false, reason: "browser disabled for this run" } : await
|
|
53764
|
+
const launched = opts.noBrowser ? { ok: false, reason: "browser disabled for this run" } : await openGuardedBrowser();
|
|
53068
53765
|
if (launched.ok) {
|
|
53069
|
-
const { context, close } = launched
|
|
53070
|
-
|
|
53766
|
+
const { context, close } = launched;
|
|
53767
|
+
let timer;
|
|
53768
|
+
const expired = new Promise((_, reject) => {
|
|
53769
|
+
const limit = opts.deadlineMs ?? VERIFY_DEADLINE_MS;
|
|
53770
|
+
timer = setTimeout(() => reject(new Error(`verify_design stopped: the page did not finish rendering within ${Math.round(limit / 1e3)} seconds, so nothing was graded.`)), limit);
|
|
53771
|
+
});
|
|
53772
|
+
const work = (async () => {
|
|
53773
|
+
if (root !== null && html2 !== null) await serveLocalPage(context, root, url, html2);
|
|
53071
53774
|
const page = context.pages()[0] ?? await context.newPage();
|
|
53072
53775
|
if (medium === "deck") {
|
|
53073
53776
|
await page.setViewportSize({ width: 1920, height: 1080 });
|
|
@@ -53087,8 +53790,8 @@ async function verifyDesign(opts) {
|
|
|
53087
53790
|
try {
|
|
53088
53791
|
await loc.scrollIntoViewIfNeeded({ timeout: 5e3 });
|
|
53089
53792
|
const png = await loc.screenshot({ type: "png", timeout: 15e3 });
|
|
53090
|
-
|
|
53091
|
-
files.push(
|
|
53793
|
+
fs41.writeFileSync(path39.join(opts.outDir, file2), png);
|
|
53794
|
+
files.push(path39.join(opts.outDir, file2));
|
|
53092
53795
|
screenshots.push(file2);
|
|
53093
53796
|
pixels.push(slidePixels(sec.index, png, file2, sec, opts.brand));
|
|
53094
53797
|
} catch {
|
|
@@ -53120,8 +53823,8 @@ async function verifyDesign(opts) {
|
|
|
53120
53823
|
const shot = "page-1440.png";
|
|
53121
53824
|
try {
|
|
53122
53825
|
const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
|
|
53123
|
-
|
|
53124
|
-
files.push(
|
|
53826
|
+
fs41.writeFileSync(path39.join(opts.outDir, shot), png);
|
|
53827
|
+
files.push(path39.join(opts.outDir, shot));
|
|
53125
53828
|
screenshots.push(shot);
|
|
53126
53829
|
} catch {
|
|
53127
53830
|
}
|
|
@@ -53129,8 +53832,8 @@ async function verifyDesign(opts) {
|
|
|
53129
53832
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
53130
53833
|
await page.evaluate(SETTLE_SCROLL_JS).catch(() => void 0);
|
|
53131
53834
|
const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
|
|
53132
|
-
|
|
53133
|
-
files.push(
|
|
53835
|
+
fs41.writeFileSync(path39.join(opts.outDir, "page-390.png"), png);
|
|
53836
|
+
files.push(path39.join(opts.outDir, "page-390.png"));
|
|
53134
53837
|
screenshots.push("page-390.png");
|
|
53135
53838
|
} catch {
|
|
53136
53839
|
}
|
|
@@ -53149,7 +53852,12 @@ async function verifyDesign(opts) {
|
|
|
53149
53852
|
rules.push(...checkConversion(conv, { brief: opts.conversion, gate: opts.gate ?? "ship", copyAxes: opts.copyAxes ?? null }, Boolean(conv)));
|
|
53150
53853
|
}
|
|
53151
53854
|
}
|
|
53855
|
+
})();
|
|
53856
|
+
work.catch(() => void 0);
|
|
53857
|
+
try {
|
|
53858
|
+
await Promise.race([work, expired]);
|
|
53152
53859
|
} finally {
|
|
53860
|
+
clearTimeout(timer);
|
|
53153
53861
|
await close();
|
|
53154
53862
|
}
|
|
53155
53863
|
} else {
|
|
@@ -53203,25 +53911,27 @@ async function verifyDesign(opts) {
|
|
|
53203
53911
|
screenshots
|
|
53204
53912
|
};
|
|
53205
53913
|
const checked = VerificationSchema.parse(report3);
|
|
53206
|
-
const jsonPath =
|
|
53207
|
-
|
|
53914
|
+
const jsonPath = path39.join(opts.outDir, "verification.json");
|
|
53915
|
+
fs41.writeFileSync(jsonPath, JSON.stringify(checked, null, 2) + "\n");
|
|
53208
53916
|
files.push(jsonPath);
|
|
53209
53917
|
const reportHtml = renderVerificationHtml(checked, opts.branding);
|
|
53210
|
-
const htmlPath =
|
|
53211
|
-
|
|
53918
|
+
const htmlPath = path39.join(opts.outDir, "verification.html");
|
|
53919
|
+
fs41.writeFileSync(htmlPath, reportHtml);
|
|
53212
53920
|
files.push(htmlPath);
|
|
53213
53921
|
return { report: checked, files, reportHtml };
|
|
53214
53922
|
}
|
|
53215
|
-
var
|
|
53923
|
+
var fs41, path39, WEB_WIDTHS, SETTLE_SCROLL_JS, FOCUS_JS, VERIFY_DEADLINE_MS;
|
|
53216
53924
|
var init_verify = __esm({
|
|
53217
53925
|
"src/command-os/design/verify.ts"() {
|
|
53218
53926
|
"use strict";
|
|
53219
|
-
|
|
53220
|
-
|
|
53221
|
-
import_node_url = require("node:url");
|
|
53927
|
+
fs41 = __toESM(require("node:fs"));
|
|
53928
|
+
path39 = __toESM(require("node:path"));
|
|
53222
53929
|
init_lint();
|
|
53223
53930
|
init_probe();
|
|
53224
53931
|
init_browser();
|
|
53932
|
+
init_guarded_browser();
|
|
53933
|
+
init_net_guard();
|
|
53934
|
+
init_local_target();
|
|
53225
53935
|
init_png();
|
|
53226
53936
|
init_slides2();
|
|
53227
53937
|
init_web2();
|
|
@@ -53259,6 +53969,7 @@ var init_verify = __esm({
|
|
|
53259
53969
|
}
|
|
53260
53970
|
return out;
|
|
53261
53971
|
}`;
|
|
53972
|
+
VERIFY_DEADLINE_MS = 3e5;
|
|
53262
53973
|
}
|
|
53263
53974
|
});
|
|
53264
53975
|
|
|
@@ -53470,17 +54181,15 @@ Fix exactly those and reply with ONLY the complete corrected HTML in one \`\`\`h
|
|
|
53470
54181
|
${html2}
|
|
53471
54182
|
\`\`\``;
|
|
53472
54183
|
}
|
|
53473
|
-
async function defaultVerify(file2, medium, brand, outDir, mode,
|
|
53474
|
-
const
|
|
53475
|
-
const { tmpdir: tmpdir2 } = await import("node:os");
|
|
54184
|
+
async function defaultVerify(file2, medium, brand, outDir, mode, conversion) {
|
|
54185
|
+
const own = safeDesignRoot((0, import_node_path9.dirname)(file2));
|
|
53476
54186
|
const r = await verifyDesign({
|
|
53477
54187
|
target: file2,
|
|
53478
54188
|
medium,
|
|
53479
54189
|
brand,
|
|
53480
54190
|
outDir,
|
|
53481
54191
|
mode,
|
|
53482
|
-
|
|
53483
|
-
profileDir: mkdtempSync((0, import_node_path9.join)(tmpdir2(), "ds-run-profile-")),
|
|
54192
|
+
roots: own ? [own, ...designRoots()] : designRoots(),
|
|
53484
54193
|
...conversion ? { pageMode: "conversion", conversion: conversion.brief, gate: conversion.gate } : {}
|
|
53485
54194
|
});
|
|
53486
54195
|
if (r.report.renderedWith !== "browser") {
|
|
@@ -53504,7 +54213,7 @@ async function runDesign(opts) {
|
|
|
53504
54213
|
const maxPasses = Math.max(1, Math.min(3, opts.maxPasses ?? 3));
|
|
53505
54214
|
const generate = opts.generate ?? claudeGenerate(opts.model ?? DEFAULT_MODEL);
|
|
53506
54215
|
const conversionRun = brief.options.pageMode === "conversion" && brief.conversion ? { brief: brief.conversion, gate: opts.gate ?? "ship" } : void 0;
|
|
53507
|
-
const verify = opts.verify ?? ((f, m, b, o, mode2) => defaultVerify(f, m, b, o, mode2,
|
|
54216
|
+
const verify = opts.verify ?? ((f, m, b, o, mode2) => defaultVerify(f, m, b, o, mode2, conversionRun));
|
|
53508
54217
|
const mode = medium === "deck" ? brief.options.mode : void 0;
|
|
53509
54218
|
const result = { ok: false, greenAt: null, passes: [], costUsd: 0, ms: 0, model: null, outDir, brandSlug: dna.slug, medium, stoppedBecause: "" };
|
|
53510
54219
|
let instruction = `${pack.markdown}
|
|
@@ -53589,6 +54298,7 @@ var init_run2 = __esm({
|
|
|
53589
54298
|
init_copy_grade();
|
|
53590
54299
|
init_lint();
|
|
53591
54300
|
init_verify();
|
|
54301
|
+
init_local_target();
|
|
53592
54302
|
init_looks();
|
|
53593
54303
|
init_store();
|
|
53594
54304
|
init_brief_validation();
|
|
@@ -53636,7 +54346,7 @@ function registerDesignStudioTools(server2) {
|
|
|
53636
54346
|
const { dna, logoSvg, logoPng } = deriveBrandDna(raw, { name, slug: slug3, rights: { declaration: a.rights, at: (/* @__PURE__ */ new Date()).toISOString() }, browser, firecrawl: fc });
|
|
53637
54347
|
if (a.rights === "none" && dna.logo) notes.push("Rights declared as none: the logo is stored for reference only; do not place it on a client-facing page.");
|
|
53638
54348
|
const written = writeBrand(dna, { logoSvg, logoPng, screenshotPng: browser?.screenshotPng });
|
|
53639
|
-
return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) =>
|
|
54349
|
+
return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) => path40.basename(f)), notes };
|
|
53640
54350
|
}
|
|
53641
54351
|
);
|
|
53642
54352
|
safeTool(
|
|
@@ -53803,18 +54513,18 @@ function registerDesignStudioTools(server2) {
|
|
|
53803
54513
|
"Check a written HTML file honours the output contract before grading: section and role markers present and consistent, no outside scripts, one h1 on a page, every image described. Deterministic; no browser. A file that fails this is not graded by verify_design.",
|
|
53804
54514
|
{ file: import_zod76.z.string().describe("Path to the .html file."), medium: import_zod76.z.enum(MEDIA) },
|
|
53805
54515
|
async ({ file: file2, medium }) => {
|
|
53806
|
-
const abs =
|
|
53807
|
-
if (!
|
|
53808
|
-
const r = lintDesignContract(
|
|
54516
|
+
const abs = path40.resolve(file2);
|
|
54517
|
+
if (!fs42.existsSync(abs)) throw new Error(`No file at ${abs}.`);
|
|
54518
|
+
const r = lintDesignContract(fs42.readFileSync(abs, "utf8"), medium);
|
|
53809
54519
|
return { ok: r.ok, problems: r.problems, warnings: r.warnings, facts: r.facts };
|
|
53810
54520
|
}
|
|
53811
54521
|
);
|
|
53812
54522
|
safeTool(
|
|
53813
54523
|
server2,
|
|
53814
54524
|
"verify_design",
|
|
53815
|
-
"Render a deck or page (a local .html or a
|
|
54525
|
+
"Render a deck or page (a local .html inside your project or brand folder, or a public web address) in a fresh, guarded browser (no saved logins, public addresses only) and measure it against the design rules and the sameness tells. Every rule on its own line: measured, proxy or judgment call, with the value and the evidence. Writes verification.json, verification.html and screenshots to outDir. PASS means zero failed measured rules and NOTHING MORE: every rule measures craft \u2014 type, spacing, colour, contrast, structure, performance and the machine-made tells \u2014 and none of them measures whether the page persuades anyone. A page can pass every rule and still be boring and unsellable. Never report a pass as a quality claim; judge the writing separately. GoHighLevel pages: pass placement:'ghl' to grade on the builder subset.",
|
|
53816
54526
|
{
|
|
53817
|
-
target: import_zod76.z.string().describe("A local .html path or
|
|
54527
|
+
target: import_zod76.z.string().describe("A local .html path inside the project folder, the brand folder or GHL_MCP_DESIGN_DIR, or a public http(s) URL."),
|
|
53818
54528
|
medium: import_zod76.z.enum(MEDIA),
|
|
53819
54529
|
brandSlug: import_zod76.z.string().optional().describe("The brand to measure against (accent share, logo, faces)."),
|
|
53820
54530
|
outDir: import_zod76.z.string().optional().describe("Where to write the report. Default: <file dir>/verify-<name>/ or ./verify-<host>/ for URLs."),
|
|
@@ -53826,7 +54536,7 @@ function registerDesignStudioTools(server2) {
|
|
|
53826
54536
|
async (a) => {
|
|
53827
54537
|
const brand = a.brandSlug ? houseLooks().find((d) => d.slug === a.brandSlug) ?? readBrand(slugify(a.brandSlug)) : void 0;
|
|
53828
54538
|
const isUrl2 = /^https?:\/\//i.test(a.target);
|
|
53829
|
-
const outDir = a.outDir ?? (isUrl2 ?
|
|
54539
|
+
const outDir = a.outDir ?? (isUrl2 ? path40.resolve(`verify-${new URL(a.target).hostname.replace(/[^a-z0-9.-]/gi, "_")}`) : path40.join(path40.dirname(path40.resolve(a.target)), `verify-${path40.basename(a.target, ".html")}`));
|
|
53830
54540
|
const r = await verifyDesign({ target: a.target, medium: a.medium, brand, outDir, placement: a.placement, mode: a.mode, ratio: a.ratio, logo: a.logo, branding: readAgencyBranding() });
|
|
53831
54541
|
const v = r.report;
|
|
53832
54542
|
const failed = v.rules.filter((x) => x.state === "MEASURED-FAIL").map((x) => `${x.ruleId} ${x.name}: ${x.measured ?? ""}`);
|
|
@@ -53841,7 +54551,7 @@ function registerDesignStudioTools(server2) {
|
|
|
53841
54551
|
judgments: v.rules.filter((x) => x.state === "ASSERTED").map((x) => `${x.ruleId} ${x.name}: ${x.detail ?? ""}`),
|
|
53842
54552
|
notApplicable: v.rules.filter((x) => x.state === "N-A").map((x) => `${x.ruleId}: ${x.detail ?? ""}`),
|
|
53843
54553
|
brandMatch: v.brandMatch,
|
|
53844
|
-
report:
|
|
54554
|
+
report: path40.join(outDir, "verification.html"),
|
|
53845
54555
|
files: r.files,
|
|
53846
54556
|
...a.placement === "ghl" && { builderOwned: builderOwnedRules().map((b) => `${b.rule.id} ${b.rule.name}: ${b.why}`) }
|
|
53847
54557
|
};
|
|
@@ -53869,11 +54579,20 @@ function registerDesignStudioTools(server2) {
|
|
|
53869
54579
|
if (!a.pageDataDownloadUrl && !a.file) throw new Error("Pass pageDataDownloadUrl (from get_page_full) or file.");
|
|
53870
54580
|
let page;
|
|
53871
54581
|
if (a.file) {
|
|
53872
|
-
const abs =
|
|
53873
|
-
if (!
|
|
53874
|
-
page = JSON.parse(
|
|
54582
|
+
const abs = path40.resolve(a.file);
|
|
54583
|
+
if (!fs42.existsSync(abs)) throw new Error(`No file at ${abs}.`);
|
|
54584
|
+
page = JSON.parse(fs42.readFileSync(abs, "utf8"));
|
|
53875
54585
|
} else {
|
|
53876
|
-
const
|
|
54586
|
+
const host = (() => {
|
|
54587
|
+
try {
|
|
54588
|
+
const u = new URL(a.pageDataDownloadUrl);
|
|
54589
|
+
return u.protocol === "https:" ? u.hostname : "";
|
|
54590
|
+
} catch {
|
|
54591
|
+
return "";
|
|
54592
|
+
}
|
|
54593
|
+
})();
|
|
54594
|
+
if (!GHL_PAGE_DATA_HOSTS.some((h) => host === h || host.endsWith("." + h))) throw new Error("pageDataDownloadUrl must be the https address get_page_full returned (GoHighLevel's storage).");
|
|
54595
|
+
const response = await guardedFetchLike(a.pageDataDownloadUrl, { headers: { Accept: "application/json,*/*" }, signal: AbortSignal.timeout(3e4), maxBytes: 5e7 });
|
|
53877
54596
|
if (!response.ok) throw new Error(`Could not download the page data: HTTP ${response.status}. Re-read get_page_full \u2014 the download URL expires.`);
|
|
53878
54597
|
page = JSON.parse(await response.text());
|
|
53879
54598
|
}
|
|
@@ -53912,9 +54631,9 @@ function registerDesignStudioTools(server2) {
|
|
|
53912
54631
|
if (!a.file && !a.text) throw new Error("Pass file or text.");
|
|
53913
54632
|
let html2 = a.text ?? "";
|
|
53914
54633
|
if (a.file) {
|
|
53915
|
-
const abs =
|
|
53916
|
-
if (!
|
|
53917
|
-
html2 =
|
|
54634
|
+
const abs = path40.resolve(a.file);
|
|
54635
|
+
if (!fs42.existsSync(abs)) throw new Error(`No file at ${abs}.`);
|
|
54636
|
+
html2 = fs42.readFileSync(abs, "utf8");
|
|
53918
54637
|
}
|
|
53919
54638
|
const r = await gradeCopy(html2, claudeGenerate(a.model), { icp: a.icp });
|
|
53920
54639
|
if (!r.grade) {
|
|
@@ -53949,13 +54668,13 @@ function registerDesignStudioTools(server2) {
|
|
|
53949
54668
|
}
|
|
53950
54669
|
);
|
|
53951
54670
|
}
|
|
53952
|
-
var import_zod76,
|
|
54671
|
+
var import_zod76, fs42, path40, GHL_PAGE_DATA_HOSTS, summarise2;
|
|
53953
54672
|
var init_design_studio = __esm({
|
|
53954
54673
|
"src/tools/design-studio.ts"() {
|
|
53955
54674
|
"use strict";
|
|
53956
54675
|
import_zod76 = require("zod");
|
|
53957
|
-
|
|
53958
|
-
|
|
54676
|
+
fs42 = __toESM(require("node:fs"));
|
|
54677
|
+
path40 = __toESM(require("node:path"));
|
|
53959
54678
|
init_tool_helpers();
|
|
53960
54679
|
init_extract();
|
|
53961
54680
|
init_extract_browser();
|
|
@@ -53974,6 +54693,8 @@ var init_design_studio = __esm({
|
|
|
53974
54693
|
init_color();
|
|
53975
54694
|
init_branding();
|
|
53976
54695
|
init_svg_sanitize();
|
|
54696
|
+
init_net_guard();
|
|
54697
|
+
GHL_PAGE_DATA_HOSTS = ["firebasestorage.googleapis.com", "storage.googleapis.com", "backend.leadconnectorhq.com"];
|
|
53977
54698
|
summarise2 = (d) => ({
|
|
53978
54699
|
slug: d.slug,
|
|
53979
54700
|
name: d.name,
|
|
@@ -54679,11 +55400,11 @@ var init_audit = __esm({
|
|
|
54679
55400
|
// src/command-os/delivery-audit/store.ts
|
|
54680
55401
|
function deliveryAuditPath() {
|
|
54681
55402
|
const override = process.env[DELIVERY_AUDIT_ENV];
|
|
54682
|
-
return override && override.trim() ? override :
|
|
55403
|
+
return override && override.trim() ? override : path41.join(appDataDir(), "delivery-audit.json");
|
|
54683
55404
|
}
|
|
54684
55405
|
function readDeliveryAudit() {
|
|
54685
55406
|
try {
|
|
54686
|
-
const parsed2 = JSON.parse(
|
|
55407
|
+
const parsed2 = JSON.parse(fs43.readFileSync(deliveryAuditPath(), "utf-8"));
|
|
54687
55408
|
if (!parsed2 || typeof parsed2 !== "object" || !parsed2.answers || typeof parsed2.answers !== "object") return { ...EMPTY3 };
|
|
54688
55409
|
return { v: 1, answers: parsed2.answers, updated_at: parsed2.updated_at };
|
|
54689
55410
|
} catch {
|
|
@@ -54694,7 +55415,7 @@ function readForWrite2() {
|
|
|
54694
55415
|
const target = deliveryAuditPath();
|
|
54695
55416
|
let raw;
|
|
54696
55417
|
try {
|
|
54697
|
-
raw =
|
|
55418
|
+
raw = fs43.readFileSync(target, "utf-8");
|
|
54698
55419
|
} catch (e) {
|
|
54699
55420
|
if (e?.code === "ENOENT") return { ...EMPTY3 };
|
|
54700
55421
|
throw new Error("Your saved audit could not be read, so nothing was changed. Nothing was lost.");
|
|
@@ -54706,7 +55427,7 @@ function readForWrite2() {
|
|
|
54706
55427
|
} catch {
|
|
54707
55428
|
const kept = `${target}.unreadable-${Date.now()}`;
|
|
54708
55429
|
try {
|
|
54709
|
-
|
|
55430
|
+
fs43.copyFileSync(target, kept);
|
|
54710
55431
|
} catch {
|
|
54711
55432
|
}
|
|
54712
55433
|
throw new Error(`Your saved audit is damaged, so nothing was written over it. A copy is at ${kept}. Start again with reset:true when you are ready.`);
|
|
@@ -54718,13 +55439,13 @@ function writeFile2(f) {
|
|
|
54718
55439
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
54719
55440
|
let renamed = false;
|
|
54720
55441
|
try {
|
|
54721
|
-
|
|
54722
|
-
|
|
55442
|
+
fs43.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
55443
|
+
fs43.renameSync(tmp, target);
|
|
54723
55444
|
renamed = true;
|
|
54724
55445
|
} finally {
|
|
54725
55446
|
if (!renamed) {
|
|
54726
55447
|
try {
|
|
54727
|
-
|
|
55448
|
+
fs43.unlinkSync(tmp);
|
|
54728
55449
|
} catch {
|
|
54729
55450
|
}
|
|
54730
55451
|
}
|
|
@@ -54745,12 +55466,12 @@ function resetDeliveryAudit(now = () => /* @__PURE__ */ new Date()) {
|
|
|
54745
55466
|
writeFile2(next);
|
|
54746
55467
|
return next;
|
|
54747
55468
|
}
|
|
54748
|
-
var
|
|
55469
|
+
var fs43, path41, EMPTY3, DELIVERY_AUDIT_ENV;
|
|
54749
55470
|
var init_store4 = __esm({
|
|
54750
55471
|
"src/command-os/delivery-audit/store.ts"() {
|
|
54751
55472
|
"use strict";
|
|
54752
|
-
|
|
54753
|
-
|
|
55473
|
+
fs43 = __toESM(require("node:fs"));
|
|
55474
|
+
path41 = __toESM(require("node:path"));
|
|
54754
55475
|
init_credentials_store();
|
|
54755
55476
|
EMPTY3 = { v: 1, answers: {} };
|
|
54756
55477
|
DELIVERY_AUDIT_ENV = "GHL_MCP_DELIVERY_AUDIT";
|
|
@@ -54975,7 +55696,7 @@ function planFingerprint(goal, steps) {
|
|
|
54975
55696
|
goal,
|
|
54976
55697
|
steps: steps.map((s) => ({ id: s.id, tool: s.tool, targets: s.targets, creates: s.creates ?? [], args: s.args ?? {} }))
|
|
54977
55698
|
});
|
|
54978
|
-
return (0,
|
|
55699
|
+
return (0, import_node_crypto15.createHash)("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 16);
|
|
54979
55700
|
}
|
|
54980
55701
|
function checkPlan(goal, findings, steps, now = /* @__PURE__ */ new Date(), knownTools) {
|
|
54981
55702
|
const refusals = [];
|
|
@@ -55192,11 +55913,11 @@ function abandon(a, why) {
|
|
|
55192
55913
|
const stillApplied = position(a).undo;
|
|
55193
55914
|
return { ok: true, automation: { ...a, phase: "abandoned", goal: a.goal, abandonedBecause: why }, stillApplied };
|
|
55194
55915
|
}
|
|
55195
|
-
var
|
|
55916
|
+
var import_node_crypto15, isExisting, FINDINGS_STALE_AFTER_MINUTES;
|
|
55196
55917
|
var init_automation = __esm({
|
|
55197
55918
|
"src/command-os/build/automation.ts"() {
|
|
55198
55919
|
"use strict";
|
|
55199
|
-
|
|
55920
|
+
import_node_crypto15 = require("node:crypto");
|
|
55200
55921
|
init_verify_findings();
|
|
55201
55922
|
isExisting = (t) => "existing" in t;
|
|
55202
55923
|
FINDINGS_STALE_AFTER_MINUTES = 60;
|
|
@@ -55206,16 +55927,16 @@ var init_automation = __esm({
|
|
|
55206
55927
|
// src/command-os/build/store.ts
|
|
55207
55928
|
function automationsPath() {
|
|
55208
55929
|
const override = process.env[AUTOMATIONS_ENV];
|
|
55209
|
-
return override && override.trim() ? override :
|
|
55930
|
+
return override && override.trim() ? override : path42.join(appDataDir(), "automations.json");
|
|
55210
55931
|
}
|
|
55211
|
-
function
|
|
55932
|
+
function parse5(raw) {
|
|
55212
55933
|
const p = JSON.parse(raw);
|
|
55213
55934
|
if (!p || typeof p !== "object" || !p.builds || typeof p.builds !== "object") throw new Error("shape");
|
|
55214
55935
|
return { v: 1, builds: p.builds };
|
|
55215
55936
|
}
|
|
55216
55937
|
function readAutomations() {
|
|
55217
55938
|
try {
|
|
55218
|
-
return
|
|
55939
|
+
return parse5(fs44.readFileSync(automationsPath(), "utf-8")).builds;
|
|
55219
55940
|
} catch {
|
|
55220
55941
|
return { ...EMPTY4.builds };
|
|
55221
55942
|
}
|
|
@@ -55224,17 +55945,17 @@ function readForWrite3() {
|
|
|
55224
55945
|
const target = automationsPath();
|
|
55225
55946
|
let raw;
|
|
55226
55947
|
try {
|
|
55227
|
-
raw =
|
|
55948
|
+
raw = fs44.readFileSync(target, "utf-8");
|
|
55228
55949
|
} catch (e) {
|
|
55229
55950
|
if (e?.code === "ENOENT") return { v: 1, builds: {} };
|
|
55230
55951
|
throw new Error("Your builds file could not be read, so nothing was changed. Nothing was lost.");
|
|
55231
55952
|
}
|
|
55232
55953
|
try {
|
|
55233
|
-
return
|
|
55954
|
+
return parse5(raw);
|
|
55234
55955
|
} catch {
|
|
55235
55956
|
const kept = `${target}.unreadable-${Date.now()}`;
|
|
55236
55957
|
try {
|
|
55237
|
-
|
|
55958
|
+
fs44.copyFileSync(target, kept);
|
|
55238
55959
|
} catch {
|
|
55239
55960
|
}
|
|
55240
55961
|
throw new Error(
|
|
@@ -55248,13 +55969,13 @@ function writeFile3(f) {
|
|
|
55248
55969
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
55249
55970
|
let renamed = false;
|
|
55250
55971
|
try {
|
|
55251
|
-
|
|
55252
|
-
|
|
55972
|
+
fs44.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
55973
|
+
fs44.renameSync(tmp, target);
|
|
55253
55974
|
renamed = true;
|
|
55254
55975
|
} finally {
|
|
55255
55976
|
if (!renamed) {
|
|
55256
55977
|
try {
|
|
55257
|
-
|
|
55978
|
+
fs44.unlinkSync(tmp);
|
|
55258
55979
|
} catch {
|
|
55259
55980
|
}
|
|
55260
55981
|
}
|
|
@@ -55280,12 +56001,12 @@ function newAutomationId(now = () => /* @__PURE__ */ new Date()) {
|
|
|
55280
56001
|
}
|
|
55281
56002
|
return id;
|
|
55282
56003
|
}
|
|
55283
|
-
var
|
|
56004
|
+
var fs44, path42, AUTOMATIONS_ENV, EMPTY4;
|
|
55284
56005
|
var init_store5 = __esm({
|
|
55285
56006
|
"src/command-os/build/store.ts"() {
|
|
55286
56007
|
"use strict";
|
|
55287
|
-
|
|
55288
|
-
|
|
56008
|
+
fs44 = __toESM(require("node:fs"));
|
|
56009
|
+
path42 = __toESM(require("node:path"));
|
|
55289
56010
|
init_credentials_store();
|
|
55290
56011
|
AUTOMATIONS_ENV = "GHL_MCP_AUTOMATIONS";
|
|
55291
56012
|
EMPTY4 = { v: 1, builds: {} };
|
|
@@ -55604,7 +56325,7 @@ function registerAgencyBuildTools(server2, knownTools, verify, activeLocation) {
|
|
|
55604
56325
|
}
|
|
55605
56326
|
const refKinds = new Map(plan.flatMap((s) => (s.creates ?? []).map((c) => [c.ref, normaliseKind(c.kind)])));
|
|
55606
56327
|
const seenKeys = new Set(found.map((f) => `${normaliseKind(f.kind)}:${f.id}`));
|
|
55607
|
-
const
|
|
56328
|
+
const hidden2 = [];
|
|
55608
56329
|
const argRefusals = [];
|
|
55609
56330
|
for (const { step: s, scan } of scans) {
|
|
55610
56331
|
if (scan.incomplete) argRefusals.push({ because: `Step "${s.id}": ${scan.incomplete}`, finding: { kind: "arguments", id: s.id } });
|
|
@@ -55619,7 +56340,7 @@ function registerAgencyBuildTools(server2, knownTools, verify, activeLocation) {
|
|
|
55619
56340
|
const key = `${kind}:${id}`;
|
|
55620
56341
|
if (seenKeys.has(key)) continue;
|
|
55621
56342
|
seenKeys.add(key);
|
|
55622
|
-
|
|
56343
|
+
hidden2.push({ kind, id, observedAt: now.toISOString() });
|
|
55623
56344
|
}
|
|
55624
56345
|
for (const { kind, id } of scan.ids) {
|
|
55625
56346
|
const refKind = refKinds.get(id);
|
|
@@ -55635,13 +56356,13 @@ function registerAgencyBuildTools(server2, knownTools, verify, activeLocation) {
|
|
|
55635
56356
|
const key = `${kind}:${id}`;
|
|
55636
56357
|
if (seenKeys.has(key)) continue;
|
|
55637
56358
|
seenKeys.add(key);
|
|
55638
|
-
|
|
56359
|
+
hidden2.push({ kind, id, observedAt: now.toISOString() });
|
|
55639
56360
|
}
|
|
55640
56361
|
}
|
|
55641
56362
|
if (argRefusals.length) {
|
|
55642
56363
|
return { planned: false, refusals: argRefusals, note: "Nothing was saved. These arguments carry ids that cannot be checked as they stand." };
|
|
55643
56364
|
}
|
|
55644
|
-
const toVerify = [...found, ...
|
|
56365
|
+
const toVerify = [...found, ...hidden2];
|
|
55645
56366
|
boundAccount = account2;
|
|
55646
56367
|
const refusals = toVerify.length === 0 ? [] : verify ? await verify(account2, toVerify) : toVerify.map((f) => ({ finding: f, because: `"${f.id}" could not be checked against any account, so it is refused rather than trusted.` }));
|
|
55647
56368
|
if (refusals.length) {
|
|
@@ -58821,13 +59542,13 @@ ${text3}`);
|
|
|
58821
59542
|
};
|
|
58822
59543
|
}
|
|
58823
59544
|
function makePipelineApi(builderClient) {
|
|
58824
|
-
return async (method,
|
|
58825
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
59545
|
+
return async (method, path45, body) => {
|
|
59546
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path45}`;
|
|
58826
59547
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
58827
59548
|
const response = await builderRequest(builderClient, method, url, sendsBody ? { body } : {});
|
|
58828
59549
|
if (!response.ok) {
|
|
58829
59550
|
const text3 = await response.text();
|
|
58830
|
-
throw new Error(`Pipeline API ${response.status}: ${method} ${
|
|
59551
|
+
throw new Error(`Pipeline API ${response.status}: ${method} ${path45}
|
|
58831
59552
|
${text3}`);
|
|
58832
59553
|
}
|
|
58833
59554
|
const text2 = await response.text();
|
|
@@ -58840,8 +59561,8 @@ ${text3}`);
|
|
|
58840
59561
|
};
|
|
58841
59562
|
}
|
|
58842
59563
|
function makeFunnelApi(builderClient) {
|
|
58843
|
-
return async (method,
|
|
58844
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
59564
|
+
return async (method, path45, body) => {
|
|
59565
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path45}`;
|
|
58845
59566
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
58846
59567
|
const response = await builderRequest(builderClient, method, url, {
|
|
58847
59568
|
...sendsBody ? { body } : {},
|
|
@@ -58854,7 +59575,7 @@ function makeFunnelApi(builderClient) {
|
|
|
58854
59575
|
});
|
|
58855
59576
|
if (!response.ok) {
|
|
58856
59577
|
const text3 = await response.text();
|
|
58857
|
-
throw new Error(`Funnel API ${response.status}: ${method} ${
|
|
59578
|
+
throw new Error(`Funnel API ${response.status}: ${method} ${path45}
|
|
58858
59579
|
${text3.slice(0, 300)}`);
|
|
58859
59580
|
}
|
|
58860
59581
|
const text2 = await response.text();
|
|
@@ -59019,8 +59740,8 @@ function makeExecuteDeps(client, builderClient, locationId2, registry2) {
|
|
|
59019
59740
|
},
|
|
59020
59741
|
fetchEmailTemplatePreview: async (previewUrl) => {
|
|
59021
59742
|
if (!/^https:\/\//i.test(previewUrl)) throw new Error(`preview URL is not https (${previewUrl.slice(0, 60)})`);
|
|
59022
|
-
const response = await
|
|
59023
|
-
if (!response.ok) throw new Error(`preview fetch HTTP ${response.status}
|
|
59743
|
+
const response = await buildNet.read(previewUrl, { headers: { Accept: "text/html,*/*" }, signal: AbortSignal.timeout(3e4) });
|
|
59744
|
+
if (!response.ok) throw new Error(`preview fetch HTTP ${response.status}`);
|
|
59024
59745
|
return response.text();
|
|
59025
59746
|
},
|
|
59026
59747
|
deleteEmailTemplate: async (templateId) => {
|
|
@@ -60071,7 +60792,7 @@ function intakeFormMatches(expected, actual) {
|
|
|
60071
60792
|
const keys = ["id", "tag", "type", "hiddenFieldQueryKey", "required", "label", "options", "picklistOptions"];
|
|
60072
60793
|
return expected.every((field, i) => keys.every((key) => field[key] === void 0 || JSON.stringify(field[key]) === JSON.stringify(actual[i]?.[key])));
|
|
60073
60794
|
}
|
|
60074
|
-
var import_zod87, customFieldItemSchema, sleep6, NOT_CONFIRMED_SCOPE, WHOLE_PRESET_NOT_KEPT, notConfirmedAsk;
|
|
60795
|
+
var import_zod87, buildNet, customFieldItemSchema, sleep6, NOT_CONFIRMED_SCOPE, WHOLE_PRESET_NOT_KEPT, notConfirmedAsk;
|
|
60075
60796
|
var init_intake_to_build = __esm({
|
|
60076
60797
|
"src/tools/intake-to-build.ts"() {
|
|
60077
60798
|
"use strict";
|
|
@@ -60105,6 +60826,8 @@ var init_intake_to_build = __esm({
|
|
|
60105
60826
|
init_features();
|
|
60106
60827
|
init_executor();
|
|
60107
60828
|
init_execute();
|
|
60829
|
+
init_net_guard();
|
|
60830
|
+
buildNet = { read: guardedFetchLike };
|
|
60108
60831
|
customFieldItemSchema = import_zod87.z.object({
|
|
60109
60832
|
id: import_zod87.z.string(),
|
|
60110
60833
|
name: import_zod87.z.string(),
|
|
@@ -60169,7 +60892,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env2 = process
|
|
|
60169
60892
|
return findings.map((finding) => ({ finding, because: `"${finding.id}" cannot be checked: no saved key for sub-account ${loc}. Register it (register_location) or point the server at it first.` }));
|
|
60170
60893
|
}
|
|
60171
60894
|
const reader = token2 ? new GHLClient({ apiKey: token2.apiKey, locationId: loc }) : client;
|
|
60172
|
-
return refuseUnseenFindings2((
|
|
60895
|
+
return refuseUnseenFindings2((path45, params) => reader.get(path45, params ? { params } : void 0), loc, findings);
|
|
60173
60896
|
}, () => client.defaultLocationId);
|
|
60174
60897
|
registerClientEngagementTools(wrap(CLIENT_ENGAGEMENTS_MODULE), registry2);
|
|
60175
60898
|
registerAuditReportTools(wrap(AUDIT_REPORT_MODULE));
|
|
@@ -60595,8 +61318,8 @@ __export(index_exports, {
|
|
|
60595
61318
|
});
|
|
60596
61319
|
module.exports = __toCommonJS(index_exports);
|
|
60597
61320
|
var dotenv2 = __toESM(require("dotenv"));
|
|
60598
|
-
var
|
|
60599
|
-
var
|
|
61321
|
+
var path44 = __toESM(require("path"));
|
|
61322
|
+
var fs46 = __toESM(require("fs"));
|
|
60600
61323
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
60601
61324
|
|
|
60602
61325
|
// src/version-compare.ts
|
|
@@ -60667,12 +61390,12 @@ init_setup_tool();
|
|
|
60667
61390
|
var import_zod88 = require("zod");
|
|
60668
61391
|
|
|
60669
61392
|
// src/skill-installer.ts
|
|
60670
|
-
var
|
|
60671
|
-
var
|
|
60672
|
-
var
|
|
60673
|
-
var
|
|
61393
|
+
var import_node_crypto16 = require("node:crypto");
|
|
61394
|
+
var fs45 = __toESM(require("node:fs"));
|
|
61395
|
+
var path43 = __toESM(require("node:path"));
|
|
61396
|
+
var os8 = __toESM(require("node:os"));
|
|
60674
61397
|
function sha2562(buf) {
|
|
60675
|
-
return (0,
|
|
61398
|
+
return (0, import_node_crypto16.createHash)("sha256").update(buf).digest("hex");
|
|
60676
61399
|
}
|
|
60677
61400
|
function isOlderRelease(candidate, installed) {
|
|
60678
61401
|
const parts = (v) => v.trim().split(".").map((n) => Number.parseInt(n, 10));
|
|
@@ -60684,9 +61407,9 @@ function isOlderRelease(candidate, installed) {
|
|
|
60684
61407
|
return false;
|
|
60685
61408
|
}
|
|
60686
61409
|
function bundledSkillsDir(baseDir) {
|
|
60687
|
-
const candidate =
|
|
61410
|
+
const candidate = path43.resolve(baseDir, "..", "skills");
|
|
60688
61411
|
try {
|
|
60689
|
-
return
|
|
61412
|
+
return fs45.statSync(candidate).isDirectory() ? candidate : null;
|
|
60690
61413
|
} catch {
|
|
60691
61414
|
return null;
|
|
60692
61415
|
}
|
|
@@ -60694,10 +61417,10 @@ function bundledSkillsDir(baseDir) {
|
|
|
60694
61417
|
function listFiles(root) {
|
|
60695
61418
|
const out = [];
|
|
60696
61419
|
const walk = (dir) => {
|
|
60697
|
-
for (const entry of
|
|
60698
|
-
const full =
|
|
61420
|
+
for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
|
|
61421
|
+
const full = path43.join(dir, entry.name);
|
|
60699
61422
|
if (entry.isDirectory()) walk(full);
|
|
60700
|
-
else if (entry.isFile()) out.push(
|
|
61423
|
+
else if (entry.isFile()) out.push(path43.relative(root, full));
|
|
60701
61424
|
}
|
|
60702
61425
|
};
|
|
60703
61426
|
walk(root);
|
|
@@ -60705,7 +61428,7 @@ function listFiles(root) {
|
|
|
60705
61428
|
}
|
|
60706
61429
|
function readMarker(markerPath) {
|
|
60707
61430
|
try {
|
|
60708
|
-
const parsed2 = JSON.parse(
|
|
61431
|
+
const parsed2 = JSON.parse(fs45.readFileSync(markerPath, "utf8"));
|
|
60709
61432
|
return parsed2 && typeof parsed2 === "object" && parsed2.files ? parsed2 : null;
|
|
60710
61433
|
} catch {
|
|
60711
61434
|
return null;
|
|
@@ -60719,35 +61442,35 @@ function installBundledSkills(opts) {
|
|
|
60719
61442
|
skippedOlderPackage: [],
|
|
60720
61443
|
unchanged: [],
|
|
60721
61444
|
errors: [],
|
|
60722
|
-
targetDir: opts.targetDir ??
|
|
61445
|
+
targetDir: opts.targetDir ?? path43.join(os8.homedir(), ".claude", "skills"),
|
|
60723
61446
|
bundledDir: null
|
|
60724
61447
|
};
|
|
60725
61448
|
const bundled = bundledSkillsDir(opts.baseDir);
|
|
60726
61449
|
result.bundledDir = bundled;
|
|
60727
61450
|
if (!bundled) return result;
|
|
60728
61451
|
try {
|
|
60729
|
-
|
|
61452
|
+
fs45.mkdirSync(result.targetDir, { recursive: true });
|
|
60730
61453
|
} catch (e) {
|
|
60731
61454
|
result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
|
|
60732
61455
|
return result;
|
|
60733
61456
|
}
|
|
60734
|
-
const markerPath =
|
|
61457
|
+
const markerPath = path43.join(result.targetDir, ".ghl-command-skills.json");
|
|
60735
61458
|
const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
|
|
60736
61459
|
const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
|
|
60737
61460
|
for (const rel of listFiles(bundled)) {
|
|
60738
61461
|
try {
|
|
60739
|
-
const src =
|
|
60740
|
-
const dest =
|
|
61462
|
+
const src = fs45.readFileSync(path43.join(bundled, rel));
|
|
61463
|
+
const dest = path43.join(result.targetDir, rel);
|
|
60741
61464
|
const srcHash = sha2562(src);
|
|
60742
61465
|
let destBuf = null;
|
|
60743
61466
|
try {
|
|
60744
|
-
destBuf =
|
|
61467
|
+
destBuf = fs45.readFileSync(dest);
|
|
60745
61468
|
} catch {
|
|
60746
61469
|
destBuf = null;
|
|
60747
61470
|
}
|
|
60748
61471
|
if (destBuf === null) {
|
|
60749
|
-
|
|
60750
|
-
|
|
61472
|
+
fs45.mkdirSync(path43.dirname(dest), { recursive: true });
|
|
61473
|
+
fs45.writeFileSync(dest, src);
|
|
60751
61474
|
newMarker.files[rel] = srcHash;
|
|
60752
61475
|
result.installed.push(rel);
|
|
60753
61476
|
continue;
|
|
@@ -60764,7 +61487,7 @@ function installBundledSkills(opts) {
|
|
|
60764
61487
|
newMarker.packageVersion = marker.packageVersion;
|
|
60765
61488
|
result.skippedOlderPackage.push(rel);
|
|
60766
61489
|
} else if (lastWritten && destHash === lastWritten) {
|
|
60767
|
-
|
|
61490
|
+
fs45.writeFileSync(dest, src);
|
|
60768
61491
|
newMarker.files[rel] = srcHash;
|
|
60769
61492
|
result.updated.push(rel);
|
|
60770
61493
|
} else {
|
|
@@ -60775,7 +61498,7 @@ function installBundledSkills(opts) {
|
|
|
60775
61498
|
}
|
|
60776
61499
|
}
|
|
60777
61500
|
try {
|
|
60778
|
-
|
|
61501
|
+
fs45.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
|
|
60779
61502
|
} catch (e) {
|
|
60780
61503
|
result.errors.push(`marker write failed: ${String(e)}`);
|
|
60781
61504
|
}
|
|
@@ -60870,11 +61593,11 @@ function agencyPrimer(p) {
|
|
|
60870
61593
|
if (staff.length) lines.push(`Their team: ${staff.join("; ")}.`);
|
|
60871
61594
|
const sops = (p.delivery?.sops ?? []).filter(Boolean);
|
|
60872
61595
|
if (sops.length) lines.push(`How they deliver, in their words: ${sops.map((s) => `"${s}"`).join(" ")}`);
|
|
60873
|
-
const
|
|
60874
|
-
if (
|
|
60875
|
-
if (
|
|
60876
|
-
if (
|
|
60877
|
-
if (
|
|
61596
|
+
const os9 = p.agencyOs;
|
|
61597
|
+
if (os9?.method) lines.push(`Their method: ${os9.method}`);
|
|
61598
|
+
if (os9?.voice) lines.push(`How they sound, and how you should sound for them: ${os9.voice}`);
|
|
61599
|
+
if (os9?.ica?.who) lines.push(`Who they serve: ${os9.ica.who}`);
|
|
61600
|
+
if (os9?.offer?.name || os9?.offer?.promise) lines.push(`Their core offer: ${[os9.offer?.name, os9.offer?.promise].filter(Boolean).join(" \u2014 ")}`);
|
|
60878
61601
|
if (!lines.length) return void 0;
|
|
60879
61602
|
lines.push("These are the agency's own details, not any client's. Client-by-client records are a tool call away, not in here.");
|
|
60880
61603
|
const text2 = lines.join("\n");
|
|
@@ -60931,7 +61654,7 @@ dotenv2.config();
|
|
|
60931
61654
|
setPkgVersion(pkg.version);
|
|
60932
61655
|
{
|
|
60933
61656
|
const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
60934
|
-
if (configDirOverride && !
|
|
61657
|
+
if (configDirOverride && !path44.isAbsolute(configDirOverride)) {
|
|
60935
61658
|
process.stderr.write(
|
|
60936
61659
|
`[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.
|
|
60937
61660
|
`
|
|
@@ -60944,20 +61667,20 @@ process.on("unhandledRejection", (reason3) => {
|
|
|
60944
61667
|
`);
|
|
60945
61668
|
});
|
|
60946
61669
|
function hardenSecretFilePerms() {
|
|
60947
|
-
const repoDir =
|
|
61670
|
+
const repoDir = path44.resolve(__dirname, "..");
|
|
60948
61671
|
const candidates = [
|
|
60949
|
-
{ file:
|
|
61672
|
+
{ file: path44.join(repoDir, "start-mcp.sh"), mode: 448 },
|
|
60950
61673
|
// Legacy registry location (pre-migration); new location lives in app-data.
|
|
60951
|
-
{ file:
|
|
61674
|
+
{ file: path44.join(repoDir, ".ghl-tokens.json"), mode: 384 },
|
|
60952
61675
|
{ file: tokenRegistryPath(), mode: 384 }
|
|
60953
61676
|
];
|
|
60954
61677
|
for (const { file: file2, mode } of candidates) {
|
|
60955
61678
|
let current;
|
|
60956
61679
|
try {
|
|
60957
|
-
if (!
|
|
60958
|
-
current =
|
|
61680
|
+
if (!fs46.existsSync(file2)) continue;
|
|
61681
|
+
current = fs46.statSync(file2).mode & 511;
|
|
60959
61682
|
if (current !== mode) {
|
|
60960
|
-
|
|
61683
|
+
fs46.chmodSync(file2, mode);
|
|
60961
61684
|
}
|
|
60962
61685
|
} catch (error) {
|
|
60963
61686
|
const message = error instanceof Error ? error.message : String(error);
|