@elitedcs/ghl-mcp 3.93.0 → 3.93.1
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 +28 -0
- package/README.md +1 -1
- package/dist/index.js +875 -473
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -156,7 +156,7 @@ var init_account_read_retry = __esm({
|
|
|
156
156
|
inBuildRequestScope = () => buildScope.getStore() !== void 0;
|
|
157
157
|
inAccountReadAttempt = () => readScope.getStore() === true;
|
|
158
158
|
withBuildRequestScope = (run, wait = sleep) => buildScope.run({ wait }, run);
|
|
159
|
-
sleep = (ms) => new Promise((
|
|
159
|
+
sleep = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
160
160
|
buildRequestWait = (ms) => (buildScope.getStore()?.wait ?? sleep)(ms);
|
|
161
161
|
}
|
|
162
162
|
});
|
|
@@ -250,9 +250,9 @@ function planFromPayload(payload) {
|
|
|
250
250
|
}
|
|
251
251
|
function legacyDeviceFingerprint() {
|
|
252
252
|
const os12 = require("node:os");
|
|
253
|
-
const
|
|
253
|
+
const crypto16 = require("node:crypto");
|
|
254
254
|
const raw = `${os12.hostname()}:${os12.userInfo().username}:${os12.platform()}:${os12.arch()}`;
|
|
255
|
-
return
|
|
255
|
+
return crypto16.createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
256
256
|
}
|
|
257
257
|
function registerDeviceIdResolver(fn) {
|
|
258
258
|
deviceIdResolver = fn;
|
|
@@ -263,7 +263,7 @@ function deviceFingerprint(opts) {
|
|
|
263
263
|
opts.persist?.(opts.cached);
|
|
264
264
|
return opts.cached;
|
|
265
265
|
}
|
|
266
|
-
const
|
|
266
|
+
const crypto16 = require("node:crypto");
|
|
267
267
|
if (!opts && deviceIdResolver) {
|
|
268
268
|
try {
|
|
269
269
|
return deviceIdResolver();
|
|
@@ -277,7 +277,7 @@ function deviceFingerprint(opts) {
|
|
|
277
277
|
const creds = store2.readCredentials();
|
|
278
278
|
if (creds?.device_id) return creds.device_id;
|
|
279
279
|
const adopted = creds?.signed_attestation ? parseAttestationPayload(creds.signed_attestation)?.device_fingerprint : void 0;
|
|
280
|
-
const id2 = adopted ||
|
|
280
|
+
const id2 = adopted || crypto16.randomBytes(8).toString("hex");
|
|
281
281
|
if (creds) {
|
|
282
282
|
try {
|
|
283
283
|
store2.updateCredentials(creds, { device_id: id2 });
|
|
@@ -289,7 +289,7 @@ function deviceFingerprint(opts) {
|
|
|
289
289
|
return legacyDeviceFingerprint();
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
const fresh =
|
|
292
|
+
const fresh = crypto16.randomBytes(8).toString("hex");
|
|
293
293
|
opts.persist?.(fresh);
|
|
294
294
|
return fresh;
|
|
295
295
|
}
|
|
@@ -811,13 +811,13 @@ function setProtectedAccount(locationId2, name2, file3 = accountRulesPath()) {
|
|
|
811
811
|
function isWriteMethod(method) {
|
|
812
812
|
return WRITE_METHODS.has(method.toUpperCase());
|
|
813
813
|
}
|
|
814
|
-
function locationIdsInRequest(
|
|
814
|
+
function locationIdsInRequest(path67, opts = {}) {
|
|
815
815
|
const found = /* @__PURE__ */ new Set();
|
|
816
816
|
const add2 = (v) => {
|
|
817
817
|
if (typeof v === "string" && v.trim()) found.add(v.trim());
|
|
818
818
|
else if (Array.isArray(v)) v.forEach(add2);
|
|
819
819
|
};
|
|
820
|
-
const seg = /\/locations\/([^/?#]+)/i.exec(
|
|
820
|
+
const seg = /\/locations\/([^/?#]+)/i.exec(path67);
|
|
821
821
|
if (seg) add2(decodeURIComponent(seg[1]));
|
|
822
822
|
for (const src of [opts.params, opts.body]) {
|
|
823
823
|
if (!src || typeof src !== "object") continue;
|
|
@@ -844,7 +844,7 @@ function protectedRefusalFor(locationId2, rulesFile) {
|
|
|
844
844
|
const name2 = rules.protected[id2];
|
|
845
845
|
return name2 ? protectedRefusalMessage(name2, false) : null;
|
|
846
846
|
}
|
|
847
|
-
function assertWriteAllowed(method,
|
|
847
|
+
function assertWriteAllowed(method, path67, opts = {}) {
|
|
848
848
|
if (!isWriteMethod(method)) return;
|
|
849
849
|
let rules;
|
|
850
850
|
try {
|
|
@@ -853,7 +853,7 @@ function assertWriteAllowed(method, path66, opts = {}) {
|
|
|
853
853
|
throw new Error(UNREADABLE_RULES_MSG);
|
|
854
854
|
}
|
|
855
855
|
if (Object.keys(rules.protected).length === 0) return;
|
|
856
|
-
for (const id2 of locationIdsInRequest(
|
|
856
|
+
for (const id2 of locationIdsInRequest(path67, opts)) {
|
|
857
857
|
const name2 = rules.protected[id2];
|
|
858
858
|
if (name2) throw new Error(protectedRefusalMessage(name2, false));
|
|
859
859
|
}
|
|
@@ -1013,8 +1013,8 @@ var init_ghl_client = __esm({
|
|
|
1013
1013
|
Version: version || GHL_API_VERSION
|
|
1014
1014
|
};
|
|
1015
1015
|
}
|
|
1016
|
-
buildUrl(
|
|
1017
|
-
const url = new URL(
|
|
1016
|
+
buildUrl(path67, params) {
|
|
1017
|
+
const url = new URL(path67, GHL_BASE_URL);
|
|
1018
1018
|
if (params) {
|
|
1019
1019
|
for (const [key2, value] of Object.entries(params)) {
|
|
1020
1020
|
if (value !== void 0 && value !== null) {
|
|
@@ -1024,19 +1024,19 @@ var init_ghl_client = __esm({
|
|
|
1024
1024
|
}
|
|
1025
1025
|
return url.toString();
|
|
1026
1026
|
}
|
|
1027
|
-
async request(method,
|
|
1027
|
+
async request(method, path67, options = {}, attempt2 = 0) {
|
|
1028
1028
|
this.signal?.throwIfAborted();
|
|
1029
1029
|
if (inBuildRequestScope() && method === "GET" && !inAccountReadAttempt()) {
|
|
1030
|
-
return retryAccountRead(() => this.request(method,
|
|
1030
|
+
return retryAccountRead(() => this.request(method, path67, options, attempt2), `${method} ${path67}`);
|
|
1031
1031
|
}
|
|
1032
1032
|
if (attempt2 === 0) {
|
|
1033
|
-
assertWriteAllowed(method,
|
|
1033
|
+
assertWriteAllowed(method, path67, {
|
|
1034
1034
|
params: options.params,
|
|
1035
1035
|
body: options.body,
|
|
1036
1036
|
defaultLocationId: this.defaultLocationId
|
|
1037
1037
|
});
|
|
1038
1038
|
}
|
|
1039
|
-
const url = this.buildUrl(
|
|
1039
|
+
const url = this.buildUrl(path67, options.params);
|
|
1040
1040
|
const headers = this.buildHeaders(options.version);
|
|
1041
1041
|
if (options.form) delete headers["Content-Type"];
|
|
1042
1042
|
if (this.beforeAttempt) {
|
|
@@ -1064,14 +1064,14 @@ var init_ghl_client = __esm({
|
|
|
1064
1064
|
clearTimeout(timeout);
|
|
1065
1065
|
this.signal?.throwIfAborted();
|
|
1066
1066
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1067
|
-
throw requestError(new Error(`Request timeout (30s): ${method} ${
|
|
1067
|
+
throw requestError(new Error(`Request timeout (30s): ${method} ${path67}`));
|
|
1068
1068
|
}
|
|
1069
1069
|
if (!inBuildRequestScope() && !options.noRetry && !options.form && attempt2 < MAX_RETRIES) {
|
|
1070
1070
|
const delay5 = computeRetryDelay(null, attempt2, BASE_DELAY_MS);
|
|
1071
|
-
process.stderr.write(`[ghl-mcp] Network error on ${method} ${
|
|
1071
|
+
process.stderr.write(`[ghl-mcp] Network error on ${method} ${path67}, retry ${attempt2 + 1}/${MAX_RETRIES} in ${delay5}ms
|
|
1072
1072
|
`);
|
|
1073
1073
|
await new Promise((r) => setTimeout(r, delay5));
|
|
1074
|
-
return this.request(method,
|
|
1074
|
+
return this.request(method, path67, options, attempt2 + 1);
|
|
1075
1075
|
}
|
|
1076
1076
|
throw requestError(error);
|
|
1077
1077
|
} finally {
|
|
@@ -1079,10 +1079,10 @@ var init_ghl_client = __esm({
|
|
|
1079
1079
|
}
|
|
1080
1080
|
if (!inBuildRequestScope() && !options.noRetry && !options.form && (response.status === 429 || response.status >= 500) && attempt2 < MAX_RETRIES) {
|
|
1081
1081
|
const delay5 = computeRetryDelay(response.headers.get("Retry-After"), attempt2, BASE_DELAY_MS);
|
|
1082
|
-
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${
|
|
1082
|
+
process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path67}, retry ${attempt2 + 1}/${MAX_RETRIES} in ${delay5}ms
|
|
1083
1083
|
`);
|
|
1084
1084
|
await new Promise((r) => setTimeout(r, delay5));
|
|
1085
|
-
return this.request(method,
|
|
1085
|
+
return this.request(method, path67, options, attempt2 + 1);
|
|
1086
1086
|
}
|
|
1087
1087
|
if (!response.ok) {
|
|
1088
1088
|
let errorBody = "";
|
|
@@ -1091,7 +1091,7 @@ var init_ghl_client = __esm({
|
|
|
1091
1091
|
} catch {
|
|
1092
1092
|
}
|
|
1093
1093
|
throw requestError(new Error(
|
|
1094
|
-
`GHL API Error ${response.status} ${response.statusText}: ${method} ${
|
|
1094
|
+
`GHL API Error ${response.status} ${response.statusText}: ${method} ${path67}
|
|
1095
1095
|
${errorBody}`
|
|
1096
1096
|
));
|
|
1097
1097
|
}
|
|
@@ -1113,20 +1113,20 @@ ${errorBody}`
|
|
|
1113
1113
|
combined?.dispose();
|
|
1114
1114
|
}
|
|
1115
1115
|
}
|
|
1116
|
-
async get(
|
|
1117
|
-
return this.request("GET",
|
|
1116
|
+
async get(path67, options) {
|
|
1117
|
+
return this.request("GET", path67, options);
|
|
1118
1118
|
}
|
|
1119
|
-
async post(
|
|
1120
|
-
return this.request("POST",
|
|
1119
|
+
async post(path67, options) {
|
|
1120
|
+
return this.request("POST", path67, options);
|
|
1121
1121
|
}
|
|
1122
|
-
async put(
|
|
1123
|
-
return this.request("PUT",
|
|
1122
|
+
async put(path67, options) {
|
|
1123
|
+
return this.request("PUT", path67, options);
|
|
1124
1124
|
}
|
|
1125
|
-
async patch(
|
|
1126
|
-
return this.request("PATCH",
|
|
1125
|
+
async patch(path67, options) {
|
|
1126
|
+
return this.request("PATCH", path67, options);
|
|
1127
1127
|
}
|
|
1128
|
-
async delete(
|
|
1129
|
-
return this.request("DELETE",
|
|
1128
|
+
async delete(path67, options) {
|
|
1129
|
+
return this.request("DELETE", path67, options);
|
|
1130
1130
|
}
|
|
1131
1131
|
/**
|
|
1132
1132
|
* Helper: resolves locationId from args or falls back to default
|
|
@@ -1703,26 +1703,26 @@ function decodeName(name2) {
|
|
|
1703
1703
|
return name2;
|
|
1704
1704
|
}
|
|
1705
1705
|
}
|
|
1706
|
-
function maskCredential(value, rec2,
|
|
1706
|
+
function maskCredential(value, rec2, path67, component = "=") {
|
|
1707
1707
|
if (!value.trim() || MASK_PRESENT_RE.test(value) || value.includes("[MASKED") || isMergeOnly(value)) return value;
|
|
1708
1708
|
const m = CREDENTIAL_SCHEME_RE.exec(value);
|
|
1709
1709
|
const real = m ? m[2] : value;
|
|
1710
1710
|
const token2 = maskToken(real);
|
|
1711
|
-
rec2.pairs.push({ path: pathKey(
|
|
1711
|
+
rec2.pairs.push({ path: pathKey(path67), group: groupKey(path67), component, token: token2, real });
|
|
1712
1712
|
return m ? `${m[1]}${token2}` : token2;
|
|
1713
1713
|
}
|
|
1714
1714
|
function looksLikeCredential(value) {
|
|
1715
1715
|
return CREDENTIAL_SCHEME_RE.test(value) && !isMergeOnly(value);
|
|
1716
1716
|
}
|
|
1717
|
-
function maskUrl(url, rec2 = newRecorder(),
|
|
1718
|
-
let out = url.replace(USERINFO_RE, (_m, pre, pass, at) => `${pre}${maskCredential(pass, rec2,
|
|
1717
|
+
function maskUrl(url, rec2 = newRecorder(), path67 = []) {
|
|
1718
|
+
let out = url.replace(USERINFO_RE, (_m, pre, pass, at) => `${pre}${maskCredential(pass, rec2, path67, "@")}${at}`);
|
|
1719
1719
|
const seen = /* @__PURE__ */ new Map();
|
|
1720
1720
|
out = out.replace(QUERY_RE, (m, sep6, name2, val) => {
|
|
1721
1721
|
const decoded2 = decodeName(name2);
|
|
1722
1722
|
const n = seen.get(decoded2) ?? 0;
|
|
1723
1723
|
seen.set(decoded2, n + 1);
|
|
1724
1724
|
if (!val || !isSensitiveName(decoded2)) return m;
|
|
1725
|
-
return `${sep6}${name2}=${maskCredential(val, rec2,
|
|
1725
|
+
return `${sep6}${name2}=${maskCredential(val, rec2, path67, `?${decoded2}#${n}`)}`;
|
|
1726
1726
|
});
|
|
1727
1727
|
return out;
|
|
1728
1728
|
}
|
|
@@ -1746,11 +1746,11 @@ function segmentsFor(items) {
|
|
|
1746
1746
|
return `[${index}]`;
|
|
1747
1747
|
});
|
|
1748
1748
|
}
|
|
1749
|
-
function maskWebhookBag(node, rec2,
|
|
1749
|
+
function maskWebhookBag(node, rec2, path67) {
|
|
1750
1750
|
if (Array.isArray(node)) {
|
|
1751
1751
|
const segs = segmentsFor(node);
|
|
1752
1752
|
node.forEach((item, i) => {
|
|
1753
|
-
const at = [...
|
|
1753
|
+
const at = [...path67, segs[i]];
|
|
1754
1754
|
if (typeof item === "string") {
|
|
1755
1755
|
if (looksLikeCredential(item)) node[i] = maskCredential(item, rec2, at);
|
|
1756
1756
|
} else maskWebhookBag(item, rec2, at);
|
|
@@ -1762,7 +1762,7 @@ function maskWebhookBag(node, rec2, path66) {
|
|
|
1762
1762
|
const label2 = labelProp ? node[labelProp] : void 0;
|
|
1763
1763
|
for (const [prop, val] of Object.entries(node)) {
|
|
1764
1764
|
if (prop === labelProp) continue;
|
|
1765
|
-
const at = [...
|
|
1765
|
+
const at = [...path67, prop];
|
|
1766
1766
|
if (typeof val === "string") {
|
|
1767
1767
|
if (prop === "value" && typeof label2 === "string") {
|
|
1768
1768
|
if (isSensitiveName(label2) || looksLikeCredential(val)) node[prop] = maskCredential(val, rec2, at);
|
|
@@ -1782,21 +1782,21 @@ function isWebhookNode(o) {
|
|
|
1782
1782
|
function isWebhookSummary(o) {
|
|
1783
1783
|
return (Array.isArray(o.headers) || isRecord2(o.headers)) && (typeof o.url === "string" || typeof o.method === "string");
|
|
1784
1784
|
}
|
|
1785
|
-
function walk(node, rec2,
|
|
1785
|
+
function walk(node, rec2, path67) {
|
|
1786
1786
|
if (Array.isArray(node)) {
|
|
1787
|
-
node.forEach((item, i) => walk(item, rec2, [...
|
|
1787
|
+
node.forEach((item, i) => walk(item, rec2, [...path67, `[${i}]`]));
|
|
1788
1788
|
return;
|
|
1789
1789
|
}
|
|
1790
1790
|
if (!isRecord2(node)) return;
|
|
1791
1791
|
if (isWebhookNode(node)) {
|
|
1792
|
-
maskWebhookBag(node.attributes, rec2, [...
|
|
1792
|
+
maskWebhookBag(node.attributes, rec2, [...path67, "attributes"]);
|
|
1793
1793
|
return;
|
|
1794
1794
|
}
|
|
1795
1795
|
if (isWebhookSummary(node)) {
|
|
1796
|
-
maskWebhookBag(node, rec2,
|
|
1796
|
+
maskWebhookBag(node, rec2, path67);
|
|
1797
1797
|
return;
|
|
1798
1798
|
}
|
|
1799
|
-
for (const [k, val] of Object.entries(node)) walk(val, rec2, [...
|
|
1799
|
+
for (const [k, val] of Object.entries(node)) walk(val, rec2, [...path67, k]);
|
|
1800
1800
|
}
|
|
1801
1801
|
function maskWorkflowSecrets(data) {
|
|
1802
1802
|
const copy = structuredClone(data);
|
|
@@ -1954,11 +1954,11 @@ function containsMask(value) {
|
|
|
1954
1954
|
function maskedWriteRefusal(where, token2) {
|
|
1955
1955
|
return `Refused: ${where} still carries a masked secret (${token2}). GHL Command hides webhook keys when it shows a workflow, and saving the placeholder would replace the real key and break that webhook. A mask is kept only in the same field of the same step it was read from (the real key is then kept for you); anywhere else, type the real value. Nothing was saved.`;
|
|
1956
1956
|
}
|
|
1957
|
-
function mapStrings(node,
|
|
1957
|
+
function mapStrings(node, path67, fn) {
|
|
1958
1958
|
if (Array.isArray(node)) {
|
|
1959
1959
|
const segs = segmentsFor(node);
|
|
1960
1960
|
node.forEach((item, i) => {
|
|
1961
|
-
const at = [...
|
|
1961
|
+
const at = [...path67, segs[i]];
|
|
1962
1962
|
if (typeof item === "string") node[i] = fn(at, item);
|
|
1963
1963
|
else mapStrings(item, at, fn);
|
|
1964
1964
|
});
|
|
@@ -1966,15 +1966,15 @@ function mapStrings(node, path66, fn) {
|
|
|
1966
1966
|
}
|
|
1967
1967
|
if (!isRecord2(node)) return;
|
|
1968
1968
|
for (const [k, v] of Object.entries(node)) {
|
|
1969
|
-
if (typeof v === "string") node[k] = fn([...
|
|
1970
|
-
else if (v !== null && typeof v === "object") mapStrings(v, [...
|
|
1969
|
+
if (typeof v === "string") node[k] = fn([...path67, k], v);
|
|
1970
|
+
else if (v !== null && typeof v === "object") mapStrings(v, [...path67, k], fn);
|
|
1971
1971
|
}
|
|
1972
1972
|
}
|
|
1973
1973
|
function stringsByGroup(node) {
|
|
1974
1974
|
const out = { byPath: /* @__PURE__ */ new Map(), byGroup: /* @__PURE__ */ new Map() };
|
|
1975
|
-
mapStrings(structuredClone(node), [], (
|
|
1976
|
-
out.byPath.set(pathKey(
|
|
1977
|
-
const g = groupKey(
|
|
1975
|
+
mapStrings(structuredClone(node), [], (path67, str16) => {
|
|
1976
|
+
out.byPath.set(pathKey(path67), str16);
|
|
1977
|
+
const g = groupKey(path67);
|
|
1978
1978
|
out.byGroup.set(g, [...out.byGroup.get(g) ?? [], str16]);
|
|
1979
1979
|
return str16;
|
|
1980
1980
|
});
|
|
@@ -2000,10 +2000,10 @@ function restoreMaskedSecrets(incoming, current) {
|
|
|
2000
2000
|
const shown = typeof action.id === "string" ? shownById.get(action.id) : void 0;
|
|
2001
2001
|
const sent = stringsByGroup(action);
|
|
2002
2002
|
const copy = structuredClone(action);
|
|
2003
|
-
mapStrings(copy, [], (
|
|
2003
|
+
mapStrings(copy, [], (path67, str16) => {
|
|
2004
2004
|
if (!MASK_PRESENT_RE.test(str16)) return str16;
|
|
2005
|
-
const pk = pathKey(
|
|
2006
|
-
const gk = groupKey(
|
|
2005
|
+
const pk = pathKey(path67);
|
|
2006
|
+
const gk = groupKey(path67);
|
|
2007
2007
|
const pick3 = (component, token2) => {
|
|
2008
2008
|
const cg = componentGroup(component);
|
|
2009
2009
|
const reals = new Set(pairs.filter((p) => p.group === gk && componentGroup(p.component) === cg && p.token === token2).map((p) => p.real));
|
|
@@ -2056,8 +2056,8 @@ var init_secret_mask = __esm({
|
|
|
2056
2056
|
SENSITIVE_NAME_RE = /auth|key|token|secret|signature|signing|passw|pwd|credential|cookie|session|hmac|private|^sig$/i;
|
|
2057
2057
|
CREDENTIAL_SCHEME_RE = /^(\s*(?:bearer|basic|token)\s+)(\S[\s\S]*)$/i;
|
|
2058
2058
|
MERGE_ONLY_RE = /^\s*(?:\{\{[^{}]+\}\}\s*)+$/;
|
|
2059
|
-
pathKey = (
|
|
2060
|
-
groupKey = (
|
|
2059
|
+
pathKey = (path67) => JSON.stringify(path67);
|
|
2060
|
+
groupKey = (path67) => JSON.stringify(path67.map((seg) => seg.replace(/#\d+$/, "")));
|
|
2061
2061
|
componentGroup = (c) => c.replace(/#\d+$/, "");
|
|
2062
2062
|
TOKEN_SRC = String.raw`\[MASKED(?: …[^\]\s]{1,4})?\]`;
|
|
2063
2063
|
USERINFO_RE = new RegExp(String.raw`^(\s*[a-z][a-z0-9+.-]*:\/\/[^/@:?#\s]+:)((?:${TOKEN_SRC}|[^/@?#\s])+)(@)`, "i");
|
|
@@ -4954,8 +4954,8 @@ function buildEvent(event, opts) {
|
|
|
4954
4954
|
}
|
|
4955
4955
|
function ephemeralId() {
|
|
4956
4956
|
if (!processEphemeralId) {
|
|
4957
|
-
const
|
|
4958
|
-
processEphemeralId =
|
|
4957
|
+
const crypto16 = require("node:crypto");
|
|
4958
|
+
processEphemeralId = crypto16.randomBytes(16).toString("hex");
|
|
4959
4959
|
}
|
|
4960
4960
|
return processEphemeralId;
|
|
4961
4961
|
}
|
|
@@ -8760,7 +8760,7 @@ function pinnedLookup(address, family) {
|
|
|
8760
8760
|
};
|
|
8761
8761
|
}
|
|
8762
8762
|
function realRequest(url, address, family, signal) {
|
|
8763
|
-
return new Promise((
|
|
8763
|
+
return new Promise((resolve15, reject) => {
|
|
8764
8764
|
const req = https.request(url, {
|
|
8765
8765
|
method: "GET",
|
|
8766
8766
|
signal,
|
|
@@ -8772,7 +8772,7 @@ function realRequest(url, address, family, signal) {
|
|
|
8772
8772
|
lookup: pinnedLookup(address, family)
|
|
8773
8773
|
}, (res) => {
|
|
8774
8774
|
const len = Number(res.headers["content-length"]);
|
|
8775
|
-
|
|
8775
|
+
resolve15({
|
|
8776
8776
|
status: res.statusCode ?? 0,
|
|
8777
8777
|
location: typeof res.headers.location === "string" ? res.headers.location : void 0,
|
|
8778
8778
|
contentType: typeof res.headers["content-type"] === "string" ? res.headers["content-type"] : void 0,
|
|
@@ -9773,11 +9773,11 @@ function registerEmailTools(server2, client) {
|
|
|
9773
9773
|
function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
|
|
9774
9774
|
if (!builderClient) return;
|
|
9775
9775
|
const client = builderClient;
|
|
9776
|
-
async function builderRequest2(method,
|
|
9777
|
-
const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${
|
|
9776
|
+
async function builderRequest2(method, path67, body) {
|
|
9777
|
+
const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path67}`, body ? { body } : {});
|
|
9778
9778
|
if (!response.ok) {
|
|
9779
9779
|
const text4 = await response.text();
|
|
9780
|
-
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${
|
|
9780
|
+
throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path67}
|
|
9781
9781
|
${text4}`);
|
|
9782
9782
|
}
|
|
9783
9783
|
const text3 = await response.text();
|
|
@@ -12075,9 +12075,9 @@ function bindingClash(number, agents, exceptAgentId, poolObjects = []) {
|
|
|
12075
12075
|
const pool = poolBinding(a);
|
|
12076
12076
|
if (pool) {
|
|
12077
12077
|
const obj2 = poolObjects.find((p) => poolId(p) === pool);
|
|
12078
|
-
const
|
|
12079
|
-
if (
|
|
12080
|
-
if (
|
|
12078
|
+
const held2 = obj2 ? poolNumbers(obj2) : [];
|
|
12079
|
+
if (held2.includes(number)) return `${number} is in number pool ${pool}, which agent "${String(a.agentName)}" (${String(a.id)}) answers through. Binding it here would take those calls away. Unbind the pool there first, or choose another number.`;
|
|
12080
|
+
if (held2.length === 0) return `Agent "${String(a.agentName)}" (${String(a.id)}) answers through number pool ${pool}, and this API does not say which numbers that pool holds, so ${number} cannot be called free. Check the pool in GoHighLevel (Settings \u2192 Phone Numbers \u2192 Number Pools); if ${number} is not in it, unbind nothing and bind by hand there, or move that agent off the pool first.`;
|
|
12081
12081
|
}
|
|
12082
12082
|
}
|
|
12083
12083
|
return null;
|
|
@@ -12128,9 +12128,9 @@ function registerVoiceAiTools(server2, shared) {
|
|
|
12128
12128
|
const poolBound = agents.filter((a) => poolBinding(a) !== null).map((a) => {
|
|
12129
12129
|
const pool = poolBinding(a);
|
|
12130
12130
|
const obj2 = poolObjects.find((p) => poolId(p) === pool);
|
|
12131
|
-
const
|
|
12132
|
-
for (const n of
|
|
12133
|
-
return { agentId: String(a.id), agentName: String(a.agentName), poolId: pool, poolNumbers:
|
|
12131
|
+
const held2 = obj2 ? poolNumbers(obj2) : [];
|
|
12132
|
+
for (const n of held2) if (!holders.has(n)) holders.set(n, { agentId: String(a.id), agentName: String(a.agentName), via: `pool ${pool}` });
|
|
12133
|
+
return { agentId: String(a.id), agentName: String(a.agentName), poolId: pool, poolNumbers: held2.length ? held2 : "unknown \u2014 this API does not list the pool's numbers" };
|
|
12134
12134
|
});
|
|
12135
12135
|
const poolRouting = poolBound.filter((p) => Array.isArray(p.poolNumbers) && p.poolNumbers.length > 0);
|
|
12136
12136
|
const poolUnproven = poolBound.filter((p) => typeof p.poolNumbers === "string");
|
|
@@ -12325,11 +12325,11 @@ function registerVoiceAiTools(server2, shared) {
|
|
|
12325
12325
|
const holder = others.find((a) => poolBinding(a) === numberPoolId);
|
|
12326
12326
|
if (holder) throw new Error(`Number pool ${numberPoolId} already answers for agent "${String(holder.agentName)}" (${String(holder.id)}). Binding it here would take those calls away. Unbind it there first (update_voice_agent with numberPoolId: null), or choose another pool.`);
|
|
12327
12327
|
const target = poolObjects.find((p) => poolId(p) === numberPoolId);
|
|
12328
|
-
const
|
|
12328
|
+
const held2 = target ? poolNumbers(target) : [];
|
|
12329
12329
|
const othersBound = others.filter((a) => boundNumbers(a).length > 0);
|
|
12330
|
-
if (
|
|
12330
|
+
if (held2.length) {
|
|
12331
12331
|
for (const a of othersBound) {
|
|
12332
|
-
const overlap = boundNumbers(a).filter((n) =>
|
|
12332
|
+
const overlap = boundNumbers(a).filter((n) => held2.includes(n));
|
|
12333
12333
|
if (overlap.length) throw new Error(`Number pool ${numberPoolId} holds ${overlap.join(", ")}, which already answers for agent "${String(a.agentName)}" (${String(a.id)}) by inboundNumber. Binding the pool here would take those calls away. Unbind there first, or choose another pool.`);
|
|
12334
12334
|
}
|
|
12335
12335
|
} else if (othersBound.length) {
|
|
@@ -15472,7 +15472,7 @@ function isPrivateAddress(ip) {
|
|
|
15472
15472
|
return false;
|
|
15473
15473
|
}
|
|
15474
15474
|
function withDeadline(p, ms, why, signal) {
|
|
15475
|
-
return new Promise((
|
|
15475
|
+
return new Promise((resolve15, reject) => {
|
|
15476
15476
|
if (signal?.aborted) {
|
|
15477
15477
|
p.catch(() => void 0);
|
|
15478
15478
|
reject(new Error("aborted"));
|
|
@@ -15487,7 +15487,7 @@ function withDeadline(p, ms, why, signal) {
|
|
|
15487
15487
|
p.then((v) => {
|
|
15488
15488
|
clearTimeout(t);
|
|
15489
15489
|
signal?.removeEventListener("abort", onAbort);
|
|
15490
|
-
|
|
15490
|
+
resolve15(v);
|
|
15491
15491
|
}, (e) => {
|
|
15492
15492
|
clearTimeout(t);
|
|
15493
15493
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -15539,7 +15539,7 @@ function closeTransferBudget(b) {
|
|
|
15539
15539
|
b.agents.https.destroy();
|
|
15540
15540
|
}
|
|
15541
15541
|
function guardedTransfer(url, init) {
|
|
15542
|
-
return new Promise((
|
|
15542
|
+
return new Promise((resolve15, reject) => {
|
|
15543
15543
|
let u;
|
|
15544
15544
|
try {
|
|
15545
15545
|
u = new URL(url);
|
|
@@ -15594,7 +15594,7 @@ function guardedTransfer(url, init) {
|
|
|
15594
15594
|
const kept = unzip ? out.filter(([k]) => !/^(?:content-encoding|content-length)$/i.test(k)) : out;
|
|
15595
15595
|
const chunks = [];
|
|
15596
15596
|
let size2 = 0;
|
|
15597
|
-
const finish3 = (incomplete) => settle2(() =>
|
|
15597
|
+
const finish3 = (incomplete) => settle2(() => resolve15({ status: res.statusCode ?? 0, headers: kept, body: Buffer.concat(chunks), incomplete }));
|
|
15598
15598
|
stream.on("data", (c) => {
|
|
15599
15599
|
size2 += c.length;
|
|
15600
15600
|
init.budget.bytesLeft -= c.length;
|
|
@@ -15650,7 +15650,7 @@ async function guardedPost(url, body, headers, timeoutMs = 2e4) {
|
|
|
15650
15650
|
const left = Math.max(1, timeoutMs - (Date.now() - started));
|
|
15651
15651
|
const u = new URL(url);
|
|
15652
15652
|
const mod = u.protocol === "https:" ? import_node_https.default : import_node_http.default;
|
|
15653
|
-
return new Promise((
|
|
15653
|
+
return new Promise((resolve15, reject) => {
|
|
15654
15654
|
let settled = false;
|
|
15655
15655
|
const chunks = [];
|
|
15656
15656
|
let size2 = 0;
|
|
@@ -15661,7 +15661,7 @@ async function guardedPost(url, body, headers, timeoutMs = 2e4) {
|
|
|
15661
15661
|
f();
|
|
15662
15662
|
};
|
|
15663
15663
|
const req = mod.request(u, { method: "POST", headers: { ...headers, "content-length": String(Buffer.byteLength(body)) }, lookup: publicOnlyLookup }, (res) => {
|
|
15664
|
-
const done = () => settle2(() =>
|
|
15664
|
+
const done = () => settle2(() => resolve15({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString("utf8") }));
|
|
15665
15665
|
res.on("data", (c) => {
|
|
15666
15666
|
size2 += c.length;
|
|
15667
15667
|
if (size2 <= 1e6) chunks.push(c);
|
|
@@ -15797,14 +15797,14 @@ async function startGuardProxy(opts = {}) {
|
|
|
15797
15797
|
client.on("close", () => up.destroy());
|
|
15798
15798
|
});
|
|
15799
15799
|
server2.on("clientError", (_e, s) => s.destroy());
|
|
15800
|
-
await new Promise((
|
|
15800
|
+
await new Promise((resolve15, reject) => {
|
|
15801
15801
|
const fail = (e) => {
|
|
15802
15802
|
server2.removeListener("listening", ok);
|
|
15803
15803
|
reject(e);
|
|
15804
15804
|
};
|
|
15805
15805
|
const ok = () => {
|
|
15806
15806
|
server2.removeListener("error", fail);
|
|
15807
|
-
|
|
15807
|
+
resolve15();
|
|
15808
15808
|
};
|
|
15809
15809
|
server2.once("error", fail);
|
|
15810
15810
|
server2.once("listening", ok);
|
|
@@ -15862,12 +15862,12 @@ async function readPageFull(client, pageId, fetcher = fetch) {
|
|
|
15862
15862
|
const usedIdToken = headers["token-id"];
|
|
15863
15863
|
headers.Origin = "https://app.gohighlevel.com";
|
|
15864
15864
|
headers.Referer = "https://app.gohighlevel.com/";
|
|
15865
|
-
const
|
|
15866
|
-
const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${
|
|
15865
|
+
const path67 = `/page/${pageId}?locationId=${client.locationId}`;
|
|
15866
|
+
const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path67}`, { method: "GET", headers });
|
|
15867
15867
|
if (!response.ok) {
|
|
15868
15868
|
if (response.status === 401) client.invalidateIdToken?.(usedIdToken);
|
|
15869
15869
|
const text4 = await response.text();
|
|
15870
|
-
throw new Error(`Funnel API Error ${response.status}: GET ${
|
|
15870
|
+
throw new Error(`Funnel API Error ${response.status}: GET ${path67}
|
|
15871
15871
|
${text4}`);
|
|
15872
15872
|
}
|
|
15873
15873
|
const text3 = await response.text();
|
|
@@ -15930,20 +15930,20 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
15930
15930
|
return;
|
|
15931
15931
|
}
|
|
15932
15932
|
const client = builderClient;
|
|
15933
|
-
async function internalGet(
|
|
15934
|
-
return client.request("GET",
|
|
15933
|
+
async function internalGet(path67) {
|
|
15934
|
+
return client.request("GET", path67);
|
|
15935
15935
|
}
|
|
15936
|
-
async function internalPost(
|
|
15937
|
-
return client.request("POST",
|
|
15936
|
+
async function internalPost(path67, body) {
|
|
15937
|
+
return client.request("POST", path67, body);
|
|
15938
15938
|
}
|
|
15939
|
-
async function internalPut(
|
|
15940
|
-
return client.request("PUT",
|
|
15939
|
+
async function internalPut(path67, body) {
|
|
15940
|
+
return client.request("PUT", path67, body);
|
|
15941
15941
|
}
|
|
15942
|
-
async function internalDelete(
|
|
15943
|
-
return client.request("DELETE",
|
|
15942
|
+
async function internalDelete(path67) {
|
|
15943
|
+
return client.request("DELETE", path67);
|
|
15944
15944
|
}
|
|
15945
|
-
async function funnelRequest(method,
|
|
15946
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
15945
|
+
async function funnelRequest(method, path67, body) {
|
|
15946
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path67}`;
|
|
15947
15947
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
15948
15948
|
const response = await builderRequest(client, method, url, {
|
|
15949
15949
|
...sendsBody ? { body } : {},
|
|
@@ -15956,7 +15956,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
|
|
|
15956
15956
|
});
|
|
15957
15957
|
if (!response.ok) {
|
|
15958
15958
|
const text4 = await response.text();
|
|
15959
|
-
throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
15959
|
+
throw new Error(`Funnel API Error ${response.status}: ${method} ${path67}
|
|
15960
15960
|
${text4}`);
|
|
15961
15961
|
}
|
|
15962
15962
|
const text3 = await response.text();
|
|
@@ -16547,17 +16547,17 @@ function requireCompany(api) {
|
|
|
16547
16547
|
}
|
|
16548
16548
|
return company;
|
|
16549
16549
|
}
|
|
16550
|
-
async function call(api,
|
|
16550
|
+
async function call(api, path67, init) {
|
|
16551
16551
|
const headers = { ...await api.headers(), Origin: "https://app.gohighlevel.com", Referer: "https://app.gohighlevel.com/" };
|
|
16552
|
-
if (MUTATING_PATHS.some((mutating) =>
|
|
16553
|
-
assertWriteAllowed("POST", SVC +
|
|
16552
|
+
if (MUTATING_PATHS.some((mutating) => path67.startsWith(mutating))) {
|
|
16553
|
+
assertWriteAllowed("POST", SVC + path67, { defaultLocationId: api.locationId() });
|
|
16554
16554
|
}
|
|
16555
16555
|
const doFetch = api.fetch ?? fetch;
|
|
16556
|
-
const response = await doFetch(SVC +
|
|
16556
|
+
const response = await doFetch(SVC + path67, { ...init, headers });
|
|
16557
16557
|
const text3 = await response.text();
|
|
16558
16558
|
if (!response.ok) {
|
|
16559
16559
|
const hint = response.status === 401 && /not authorized to access this company/i.test(text3) ? " \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." : "";
|
|
16560
|
-
throw new Error(`GoHighLevel template library ${response.status} on ${
|
|
16560
|
+
throw new Error(`GoHighLevel template library ${response.status} on ${path67}${hint}
|
|
16561
16561
|
${text3.slice(0, 300)}`);
|
|
16562
16562
|
}
|
|
16563
16563
|
return text3 ? JSON.parse(text3) : {};
|
|
@@ -16915,12 +16915,12 @@ function normaliseColour(value) {
|
|
|
16915
16915
|
function renderedPalette(envelope) {
|
|
16916
16916
|
const defs = pageVariables(envelope);
|
|
16917
16917
|
const paints = [];
|
|
16918
|
-
const walk2 = (node,
|
|
16919
|
-
if (Array.isArray(node)) return void node.forEach((x, i) => walk2(x, `${
|
|
16918
|
+
const walk2 = (node, path67, prop) => {
|
|
16919
|
+
if (Array.isArray(node)) return void node.forEach((x, i) => walk2(x, `${path67}[${i}]`, prop));
|
|
16920
16920
|
if (isRecord11(node)) {
|
|
16921
16921
|
for (const [k, x] of Object.entries(node)) {
|
|
16922
16922
|
if (DEFINITION_KEYS.has(k) || k.startsWith("--")) continue;
|
|
16923
|
-
walk2(x, `${
|
|
16923
|
+
walk2(x, `${path67}.${k}`, WRAPPER_KEYS.has(k) ? prop : k);
|
|
16924
16924
|
}
|
|
16925
16925
|
return;
|
|
16926
16926
|
}
|
|
@@ -16930,7 +16930,7 @@ function renderedPalette(envelope) {
|
|
|
16930
16930
|
const resolved2 = resolveValue(raw, defs);
|
|
16931
16931
|
const colour = normaliseColour(resolved2);
|
|
16932
16932
|
if (colour === null && resolved2 !== null) return;
|
|
16933
|
-
paints.push({ path:
|
|
16933
|
+
paints.push({ path: path67, prop, kind: kindOf(prop), colour });
|
|
16934
16934
|
};
|
|
16935
16935
|
walk2(envelope, "", "");
|
|
16936
16936
|
const face = (name2, settingsKey) => {
|
|
@@ -17025,7 +17025,7 @@ var init_rendered_palette = __esm({
|
|
|
17025
17025
|
VISIBLE_CHANGE = 0.05;
|
|
17026
17026
|
BARELY_SHARE = 0.5;
|
|
17027
17027
|
CARRIES_COLOUR = 0.02;
|
|
17028
|
-
paintGroup = (
|
|
17028
|
+
paintGroup = (path67) => /^\.(sections|popups)\[\d+\]/.exec(path67)?.[0]?.slice(1) ?? "page";
|
|
17029
17029
|
looksDifferent = (a, b) => {
|
|
17030
17030
|
if (a === b) return false;
|
|
17031
17031
|
if (a === "transparent" || b === "transparent" || a.startsWith("rgba") || b.startsWith("rgba")) return a !== b;
|
|
@@ -22272,9 +22272,9 @@ function removalHoldBack(opts) {
|
|
|
22272
22272
|
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`;
|
|
22273
22273
|
return null;
|
|
22274
22274
|
}
|
|
22275
|
-
function brandedPath(
|
|
22275
|
+
function brandedPath(path67, name2, taken) {
|
|
22276
22276
|
const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
22277
|
-
const base = normalize2(
|
|
22277
|
+
const base = normalize2(path67 ?? "") || name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
|
|
22278
22278
|
const used = new Set([...taken].map(normalize2));
|
|
22279
22279
|
let candidate = `${base}-branded`;
|
|
22280
22280
|
for (let n = 2; used.has(candidate); n++) candidate = `${base}-branded-${n}`;
|
|
@@ -22302,9 +22302,9 @@ function rewriteTemplateLinks(envelope, destinations) {
|
|
|
22302
22302
|
representation = "page";
|
|
22303
22303
|
matches = destinations.filter((d2) => d2.pageId === preview[2]);
|
|
22304
22304
|
} else if (!/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(from) && !from.startsWith("#")) {
|
|
22305
|
-
const [
|
|
22306
|
-
suffix = from.slice(
|
|
22307
|
-
matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(
|
|
22305
|
+
const [path67] = from.split(/[?#]/);
|
|
22306
|
+
suffix = from.slice(path67.length);
|
|
22307
|
+
matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(path67));
|
|
22308
22308
|
}
|
|
22309
22309
|
}
|
|
22310
22310
|
if (matches.length !== 1) {
|
|
@@ -22387,8 +22387,8 @@ function registerPageStudioTools(server2, builderClient) {
|
|
|
22387
22387
|
}
|
|
22388
22388
|
return { map: out, truncated };
|
|
22389
22389
|
}
|
|
22390
|
-
async function funnelRequest(method,
|
|
22391
|
-
const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${
|
|
22390
|
+
async function funnelRequest(method, path67, body) {
|
|
22391
|
+
const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path67}`, {
|
|
22392
22392
|
...body ? { body } : {},
|
|
22393
22393
|
headers: {
|
|
22394
22394
|
// Internal-API calls require the app origin (autosave needs the
|
|
@@ -22397,7 +22397,7 @@ function registerPageStudioTools(server2, builderClient) {
|
|
|
22397
22397
|
Referer: "https://app.gohighlevel.com/"
|
|
22398
22398
|
}
|
|
22399
22399
|
});
|
|
22400
|
-
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${
|
|
22400
|
+
if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path67}
|
|
22401
22401
|
${await response.text()}`);
|
|
22402
22402
|
const text3 = await response.text();
|
|
22403
22403
|
return text3 ? JSON.parse(text3) : {};
|
|
@@ -22905,7 +22905,7 @@ ${await response.text()}`);
|
|
|
22905
22905
|
}
|
|
22906
22906
|
const confirmed = /* @__PURE__ */ new Set();
|
|
22907
22907
|
for (let attempt2 = 0; accepted.length && attempt2 < 5; attempt2++) {
|
|
22908
|
-
if (attempt2) await new Promise((
|
|
22908
|
+
if (attempt2) await new Promise((resolve15) => setTimeout(resolve15, attempt2 * 1e3));
|
|
22909
22909
|
try {
|
|
22910
22910
|
const listed = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}`);
|
|
22911
22911
|
const current = listed.funnels?.find((item) => item._id === funnel._id);
|
|
@@ -23269,13 +23269,13 @@ function cleanFormDataForSave(formData) {
|
|
|
23269
23269
|
const cleaned = dropRedundantCaseCollidingKeys(formData);
|
|
23270
23270
|
return isJsonObject(cleaned) ? cleaned : formData;
|
|
23271
23271
|
}
|
|
23272
|
-
async function formApiRequest(client, method,
|
|
23273
|
-
const url = `https://backend.leadconnectorhq.com/forms${
|
|
23272
|
+
async function formApiRequest(client, method, path67, body) {
|
|
23273
|
+
const url = `https://backend.leadconnectorhq.com/forms${path67}`;
|
|
23274
23274
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
23275
23275
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
23276
23276
|
if (!response.ok) {
|
|
23277
23277
|
const text4 = await response.text();
|
|
23278
|
-
throw new Error(`Form API Error ${response.status}: ${method} ${
|
|
23278
|
+
throw new Error(`Form API Error ${response.status}: ${method} ${path67}
|
|
23279
23279
|
${text4}`);
|
|
23280
23280
|
}
|
|
23281
23281
|
const text3 = await response.text();
|
|
@@ -23317,7 +23317,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
23317
23317
|
);
|
|
23318
23318
|
return;
|
|
23319
23319
|
}
|
|
23320
|
-
const formRequest = (method,
|
|
23320
|
+
const formRequest = (method, path67, body) => formApiRequest(client, method, path67, body);
|
|
23321
23321
|
server2.tool(
|
|
23322
23322
|
"get_form_full",
|
|
23323
23323
|
"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.",
|
|
@@ -23451,10 +23451,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
|
|
|
23451
23451
|
},
|
|
23452
23452
|
async ({ formId, limit, skip }) => {
|
|
23453
23453
|
try {
|
|
23454
|
-
let
|
|
23455
|
-
if (formId)
|
|
23456
|
-
if (skip)
|
|
23457
|
-
const result = await formRequest("GET",
|
|
23454
|
+
let path67 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
|
|
23455
|
+
if (formId) path67 += `&formId=${formId}`;
|
|
23456
|
+
if (skip) path67 += `&skip=${skip}`;
|
|
23457
|
+
const result = await formRequest("GET", path67);
|
|
23458
23458
|
return {
|
|
23459
23459
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
23460
23460
|
};
|
|
@@ -23559,7 +23559,7 @@ function parseContact(raw) {
|
|
|
23559
23559
|
};
|
|
23560
23560
|
}
|
|
23561
23561
|
async function searchByEmail(client, locationId2, email) {
|
|
23562
|
-
const paged = await readContactPages((
|
|
23562
|
+
const paged = await readContactPages((path67, o) => client.get(path67, o), { locationId: locationId2, query: email, limit: 20 });
|
|
23563
23563
|
if (!paged) throw new SearchUnreadable("the contact search answered in a shape that could not be read");
|
|
23564
23564
|
const want = email.toLowerCase();
|
|
23565
23565
|
const ids = [];
|
|
@@ -24044,13 +24044,13 @@ var init_funnel_qa = __esm({
|
|
|
24044
24044
|
function registerPipelineBuilderTools(server2, builderClient) {
|
|
24045
24045
|
if (!builderClient) return;
|
|
24046
24046
|
const client = builderClient;
|
|
24047
|
-
async function pipelineRequest(method,
|
|
24048
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
24047
|
+
async function pipelineRequest(method, path67, body) {
|
|
24048
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path67}`;
|
|
24049
24049
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT" || method === "PATCH");
|
|
24050
24050
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
24051
24051
|
if (!response.ok) {
|
|
24052
24052
|
const text4 = await response.text();
|
|
24053
|
-
throw new Error(`Pipeline API Error ${response.status}: ${method} ${
|
|
24053
|
+
throw new Error(`Pipeline API Error ${response.status}: ${method} ${path67}
|
|
24054
24054
|
${text4}`);
|
|
24055
24055
|
}
|
|
24056
24056
|
const text3 = await response.text();
|
|
@@ -26476,7 +26476,7 @@ function calendarSetupDetail() {
|
|
|
26476
26476
|
}
|
|
26477
26477
|
function openBrowser(url) {
|
|
26478
26478
|
const [file3, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
26479
|
-
return new Promise((
|
|
26479
|
+
return new Promise((resolve15) => (0, import_node_child_process.execFile)(file3, args, { timeout: 5e3 }, (error) => resolve15(!error)));
|
|
26480
26480
|
}
|
|
26481
26481
|
function parsed(schema, value) {
|
|
26482
26482
|
const result = schema.safeParse(value);
|
|
@@ -26573,16 +26573,16 @@ function createGoogleCalendar(deps = {}) {
|
|
|
26573
26573
|
requireOnlyCalendar(result.scope);
|
|
26574
26574
|
return result.access_token;
|
|
26575
26575
|
}
|
|
26576
|
-
async function pages(
|
|
26576
|
+
async function pages(path67, access, item, query = {}) {
|
|
26577
26577
|
const result = [];
|
|
26578
26578
|
const seen = /* @__PURE__ */ new Set();
|
|
26579
26579
|
let pageToken;
|
|
26580
26580
|
for (let page2 = 0; page2 < 100; page2++) {
|
|
26581
|
-
const url = new URL(`${API}${
|
|
26581
|
+
const url = new URL(`${API}${path67}`);
|
|
26582
26582
|
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
|
26583
26583
|
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
|
26584
26584
|
const data = parsed(
|
|
26585
|
-
import_zod53.z.object({ items: import_zod53.z.array(item).optional(), nextPageToken: import_zod53.z.string().optional(), kind: import_zod53.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (
|
|
26585
|
+
import_zod53.z.object({ items: import_zod53.z.array(item).optional(), nextPageToken: import_zod53.z.string().optional(), kind: import_zod53.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path67.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
|
|
26586
26586
|
await request2(url.href, { headers: { Authorization: `Bearer ${access}` } })
|
|
26587
26587
|
);
|
|
26588
26588
|
result.push(...data.items ?? []);
|
|
@@ -26634,9 +26634,9 @@ function createGoogleCalendar(deps = {}) {
|
|
|
26634
26634
|
server2.requestTimeout = 5e3;
|
|
26635
26635
|
server2.headersTimeout = 5e3;
|
|
26636
26636
|
try {
|
|
26637
|
-
await new Promise((
|
|
26637
|
+
await new Promise((resolve15, reject) => {
|
|
26638
26638
|
server2.once("error", reject);
|
|
26639
|
-
server2.listen(0, "127.0.0.1",
|
|
26639
|
+
server2.listen(0, "127.0.0.1", resolve15);
|
|
26640
26640
|
});
|
|
26641
26641
|
} catch {
|
|
26642
26642
|
server2.close();
|
|
@@ -27561,7 +27561,7 @@ var init_supabase = __esm({
|
|
|
27561
27561
|
}
|
|
27562
27562
|
token;
|
|
27563
27563
|
fetchFn;
|
|
27564
|
-
async call(what,
|
|
27564
|
+
async call(what, path67, init = {}) {
|
|
27565
27565
|
const headers = { Authorization: `Bearer ${this.token}`, Accept: "application/json" };
|
|
27566
27566
|
let body;
|
|
27567
27567
|
if (init.form) body = init.form;
|
|
@@ -27571,7 +27571,7 @@ var init_supabase = __esm({
|
|
|
27571
27571
|
}
|
|
27572
27572
|
let res;
|
|
27573
27573
|
try {
|
|
27574
|
-
res = await this.fetchFn(`${MANAGEMENT_API}${
|
|
27574
|
+
res = await this.fetchFn(`${MANAGEMENT_API}${path67}`, { method: init.method ?? "GET", headers, body, redirect: "error" });
|
|
27575
27575
|
} catch {
|
|
27576
27576
|
throw new BrainApiError(`${what}: Supabase could not be reached. Nothing on your side changed.`, null);
|
|
27577
27577
|
}
|
|
@@ -28298,8 +28298,8 @@ function redactConnectionKey(value, key2) {
|
|
|
28298
28298
|
return value;
|
|
28299
28299
|
}
|
|
28300
28300
|
function stripeFetcher(key2, signal) {
|
|
28301
|
-
return async (
|
|
28302
|
-
const url = new URL(`https://api.stripe.com${
|
|
28301
|
+
return async (path67, query) => {
|
|
28302
|
+
const url = new URL(`https://api.stripe.com${path67}`);
|
|
28303
28303
|
for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
|
|
28304
28304
|
const res = await fetch(url, { headers: { Authorization: `Bearer ${key2}` }, signal });
|
|
28305
28305
|
if (!res.ok) {
|
|
@@ -28448,11 +28448,11 @@ var init_handlers = __esm({
|
|
|
28448
28448
|
});
|
|
28449
28449
|
|
|
28450
28450
|
// src/command-os/connectors/stripe-read.ts
|
|
28451
|
-
async function listAll(f,
|
|
28451
|
+
async function listAll(f, path67, schema, extra = {}) {
|
|
28452
28452
|
const out = [];
|
|
28453
28453
|
let after;
|
|
28454
28454
|
for (let page2 = 0; page2 < 20; page2++) {
|
|
28455
|
-
const result = import_zod58.z.object({ data: import_zod58.z.array(schema), has_more: import_zod58.z.boolean().optional() }).parse(await f(
|
|
28455
|
+
const result = import_zod58.z.object({ data: import_zod58.z.array(schema), has_more: import_zod58.z.boolean().optional() }).parse(await f(path67, { limit: "100", ...extra, ...after ? { starting_after: after } : {} }));
|
|
28456
28456
|
out.push(...result.data);
|
|
28457
28457
|
if (!result.has_more) return out;
|
|
28458
28458
|
const last = import_zod58.z.object({ id: import_zod58.z.string().min(1) }).parse(result.data.at(-1));
|
|
@@ -32290,8 +32290,8 @@ function classifyBrainWorkflow(read2, functionUrl) {
|
|
|
32290
32290
|
return null;
|
|
32291
32291
|
}
|
|
32292
32292
|
if (u.origin !== base.origin || u.search !== "" || u.hash !== "") return null;
|
|
32293
|
-
const
|
|
32294
|
-
return
|
|
32293
|
+
const path67 = u.pathname.replace(/\/+$/, "");
|
|
32294
|
+
return path67 === `${base.pathname}/event` ? "/event" : path67 === `${base.pathname}/reply` ? "/reply" : null;
|
|
32295
32295
|
};
|
|
32296
32296
|
const urlOf = (a) => a?.attributes?.url;
|
|
32297
32297
|
const hits = steps.filter((s) => exact(urlOf(s)) !== null);
|
|
@@ -32390,8 +32390,8 @@ async function brainWireGhl(deps, args) {
|
|
|
32390
32390
|
return { ok: false, wrote: "nothing", error: e instanceof Error ? e.message : String(e) };
|
|
32391
32391
|
}
|
|
32392
32392
|
const specs = wiringSpecs({ testTag: args.testTag, leadTag });
|
|
32393
|
-
const
|
|
32394
|
-
const clean5 = (e) => scrub(e instanceof Error ? e.message : String(e),
|
|
32393
|
+
const held2 = [];
|
|
32394
|
+
const clean5 = (e) => scrub(e instanceof Error ? e.message : String(e), held2);
|
|
32395
32395
|
const survey = async () => {
|
|
32396
32396
|
const refuse4 = (error) => ({ refusal: { ok: false, wrote: "nothing", error } });
|
|
32397
32397
|
const meta = deps.meta();
|
|
@@ -32417,7 +32417,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32417
32417
|
if (c.kind === "uncertain") return refuse4(`Workflow ${id2} reaches this brain, but ${c.why}, so the brain cannot tell what it is. Nothing was changed. ${RESTART}`);
|
|
32418
32418
|
if (c.kind === "brain") {
|
|
32419
32419
|
if (known && known !== c.signal) return refuse4(`Workflow ${id2} is saved on this computer as the brain's ${known} workflow but now reports ${c.signal}, so the brain cannot tell what it is. Nothing was changed. ${RESTART}`);
|
|
32420
|
-
|
|
32420
|
+
held2.push(c.key);
|
|
32421
32421
|
found.push({ id: id2, signal: c.signal, stepId: c.stepId, key: c.key, keyHash: await hashKey2(c.key) });
|
|
32422
32422
|
} else if (known && shape.kind === "empty") {
|
|
32423
32423
|
found.push({ id: id2, signal: known, stepId: null, key: null, keyHash: null });
|
|
@@ -32440,7 +32440,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32440
32440
|
if (c.kind === "uncertain") return refuse4(`Workflow ${id2} reaches this brain, but ${c.why}, so the brain cannot tell what it is. Nothing was changed. ${RESTART}`);
|
|
32441
32441
|
if (c.kind === "brain") {
|
|
32442
32442
|
if (known !== c.signal) return refuse4(`Workflow ${id2} is saved on this computer as the brain's ${known} workflow but now reports ${c.signal}, so the brain cannot tell what it is. Nothing was changed. ${RESTART}`);
|
|
32443
|
-
|
|
32443
|
+
held2.push(c.key);
|
|
32444
32444
|
found.push({ id: id2, signal: c.signal, stepId: c.stepId, key: c.key, keyHash: await hashKey2(c.key) });
|
|
32445
32445
|
} else if (shape.kind === "empty") found.push({ id: id2, signal: known, stepId: null, key: null, keyHash: null });
|
|
32446
32446
|
else return refuse4(`Workflow ${id2} (${workflowToFind(known, id2)}) no longer holds the brain's one webhook step. Nothing was changed. ${RESTART}`);
|
|
@@ -32537,13 +32537,13 @@ async function brainWireGhl(deps, args) {
|
|
|
32537
32537
|
else if (now.keyState.wiredAt !== null) {
|
|
32538
32538
|
if (!onlyKnownKeyless([...bySignal.values()], now.keyState.hash)) return { ok: false, wrote: "nothing", error: NO_CURRENT_KEY };
|
|
32539
32539
|
writeKey = newKey("write");
|
|
32540
|
-
|
|
32540
|
+
held2.push(writeKey);
|
|
32541
32541
|
if (!await store2.replaceFirstWriteKey(client.id, now.keyState.hash, await hashKey2(writeKey), lease)) {
|
|
32542
32542
|
return { ok: false, wrote: "nothing", error: "The brain's key changed while this ran, or another operation took over, so nothing was changed. Run brain_wire_ghl again." };
|
|
32543
32543
|
}
|
|
32544
32544
|
} else {
|
|
32545
32545
|
writeKey = newKey("write");
|
|
32546
|
-
|
|
32546
|
+
held2.push(writeKey);
|
|
32547
32547
|
const leftovers = [...bySignal.values()].filter((w) => w.keyHash !== null).map((w) => [w.id, w.keyHash]);
|
|
32548
32548
|
if (leftovers.length) deps.saveMeta({ [leftoversKey]: JSON.stringify(leftovers) });
|
|
32549
32549
|
if (!await store2.setFirstWriteKey(client.id, now.keyState.hash, await hashKey2(writeKey), lease)) {
|
|
@@ -32679,12 +32679,12 @@ function keyOf(a) {
|
|
|
32679
32679
|
async function clearSetupLock(deps, api, ref, args) {
|
|
32680
32680
|
const table = await api.query(ref, "select to_regclass('brain.setup_lease') is not null as present;");
|
|
32681
32681
|
if (table[0]?.present !== true) return { ok: true, wrote: "nothing", note: "No setup lock is held on this project." };
|
|
32682
|
-
const
|
|
32683
|
-
if (!
|
|
32684
|
-
const plan = { mode: "clear_setup_lock", project: ref, lock: { takenAt:
|
|
32685
|
-
const g = gate2(plan, args, deps, { operation: "brain_retire:clear_setup_lock", projectRef: ref, clientId: "setup-lock", locationId:
|
|
32682
|
+
const held2 = import_zod63.z.array(import_zod63.z.object({ holder: import_zod63.z.string(), taken_at: import_zod63.z.string() })).parse(await api.query(ref, "select holder, taken_at::text as taken_at from brain.setup_lease;"));
|
|
32683
|
+
if (!held2.length) return { ok: true, wrote: "nothing", note: "No setup lock is held on this project." };
|
|
32684
|
+
const plan = { mode: "clear_setup_lock", project: ref, lock: { takenAt: held2[0].taken_at }, writes: "Deletes the setup lock row only. Nothing else in the project changes. Only do this if no brain_setup is running for this project right now." };
|
|
32685
|
+
const g = gate2(plan, args, deps, { operation: "brain_retire:clear_setup_lock", projectRef: ref, clientId: "setup-lock", locationId: held2[0].holder });
|
|
32686
32686
|
if (!g.go) return g.response;
|
|
32687
|
-
await api.query(ref, `delete from brain.setup_lease where holder = '${
|
|
32687
|
+
await api.query(ref, `delete from brain.setup_lease where holder = '${held2[0].holder.replace(/'/g, "''")}';`);
|
|
32688
32688
|
const left = await api.query(ref, "select 1 from brain.setup_lease;");
|
|
32689
32689
|
return { ok: left.length === 0, cleared: left.length === 0, next: "Run brain_setup again." };
|
|
32690
32690
|
}
|
|
@@ -33147,8 +33147,8 @@ function normUrl(u) {
|
|
|
33147
33147
|
try {
|
|
33148
33148
|
const url = new URL(scheme + t);
|
|
33149
33149
|
const host = url.hostname.replace(/\.$/, "").replace(/^www\./, "");
|
|
33150
|
-
const
|
|
33151
|
-
return `${host}${url.port ? `:${url.port}` : ""}${
|
|
33150
|
+
const path67 = url.pathname.replace(/\/+$/, "");
|
|
33151
|
+
return `${host}${url.port ? `:${url.port}` : ""}${path67}`.toLowerCase();
|
|
33152
33152
|
} catch {
|
|
33153
33153
|
return t.replace(/^https?:\/\//, "").replace(/^[^/@]*@/, "").replace(/^www\./, "").replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
33154
33154
|
}
|
|
@@ -33565,8 +33565,8 @@ async function fingerprint(body, firstName) {
|
|
|
33565
33565
|
let at = 0;
|
|
33566
33566
|
for (const l of linksIn(t)) {
|
|
33567
33567
|
out += t.slice(at, l.index);
|
|
33568
|
-
const
|
|
33569
|
-
out += ` link ${
|
|
33568
|
+
const path67 = l.canon.split("/").filter((seg) => seg && !/^(?=.*\d)[\p{L}\p{N}_-]{12,}$/u.test(seg) && !/^[0-9a-f-]{16,}$/i.test(seg));
|
|
33569
|
+
out += ` link ${path67.join(" ").replace(/[^\p{L}\p{N}]+/gu, " ")} `;
|
|
33570
33570
|
at = l.index + l.length;
|
|
33571
33571
|
}
|
|
33572
33572
|
t = (out + t.slice(at)).replace(/[\p{L}\p{N}._%+-]+@[\p{L}\p{N}.-]+\.[\p{L}]{2,}/gu, " ").replace(/\b\d{4}-\d{1,2}-\d{1,2}\b|\b\d{1,2}[/.-]\d{1,2}(?:[/.-]\d{2,4})?\b/g, " ").replace(/\b\d{1,2}(?::\d{2})?\s*(?:[ap]\.?\s?m\b\.?)|\b\d{1,2}:\d{2}\b/g, " ").replace(/(?:[$€£¥₹]|\b(?:usd|eur|gbp|aed|sar|cad|aud)\b)\s?\d[\d,]*(?:\.\d+)?|\b\d[\d,]*(?:\.\d+)?\s?(?:%|percent\b|dollars?\b|(?:usd|eur|gbp|aed|sar|cad|aud)\b)/g, " ").replace(/\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?\s+\d{1,2}(?:st|nd|rd|th)?\b(?:,?\s+\d{4}\b)?/g, " ").replace(/\b\d{1,2}(?:st|nd|rd|th)?\s+(?:of\s+)?(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?(?:,?\s+\d{4}\b)?/g, " ").replace(/\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?,?\s+\d{4}\b/g, " ").replace(/\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\.?\b/g, " ").replace(/\b(?:mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)(?:day|sday|nesday|rsday|urday)?\b/g, " ").replace(/[^\p{L}\p{M}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
@@ -33864,15 +33864,15 @@ var init_cockpit_budget = __esm({
|
|
|
33864
33864
|
|
|
33865
33865
|
// src/command-os/brain/account-proposals.ts
|
|
33866
33866
|
function clientSettingsGet(client) {
|
|
33867
|
-
return (
|
|
33867
|
+
return (path67, params, opts) => client.get(path67, opts?.onText ? { params, onText: opts.onText } : { params });
|
|
33868
33868
|
}
|
|
33869
|
-
function settingsPathAllowed(
|
|
33870
|
-
return
|
|
33869
|
+
function settingsPathAllowed(path67, locationId2) {
|
|
33870
|
+
return path67 === "/calendars/" || path67 === "/products/" || path67 === "/users/" || /^\/products\/[A-Za-z0-9_-]{1,64}\/price$/.test(path67) || path67 === `/locations/${encodeURIComponent(locationId2)}`;
|
|
33871
33871
|
}
|
|
33872
33872
|
function settingsOnly(locationId2, get2) {
|
|
33873
|
-
return async (
|
|
33874
|
-
if (!settingsPathAllowed(
|
|
33875
|
-
return opts ? get2(
|
|
33873
|
+
return async (path67, params, opts) => {
|
|
33874
|
+
if (!settingsPathAllowed(path67, locationId2)) throw new NotASettingError(`${path67} is not an account setting, so it was not read`);
|
|
33875
|
+
return opts ? get2(path67, params, opts) : get2(path67, params);
|
|
33876
33876
|
};
|
|
33877
33877
|
}
|
|
33878
33878
|
function openHoursOf(raw) {
|
|
@@ -34076,10 +34076,10 @@ function continuationOf(raw) {
|
|
|
34076
34076
|
async function readAccountSettings(get2, locationId2) {
|
|
34077
34077
|
const out = { candidates: [], notRead: [], empty: [], held: [] };
|
|
34078
34078
|
const more = /* @__PURE__ */ new Map();
|
|
34079
|
-
const read2 = async (what, schema,
|
|
34079
|
+
const read2 = async (what, schema, path67, params, onText) => {
|
|
34080
34080
|
let raw;
|
|
34081
34081
|
try {
|
|
34082
|
-
raw = await (onText ? get2(
|
|
34082
|
+
raw = await (onText ? get2(path67, params, { onText }) : get2(path67, params));
|
|
34083
34083
|
} catch (e) {
|
|
34084
34084
|
const budget = budgetFailureText(e);
|
|
34085
34085
|
out.notRead.push(budget !== null ? `Not read, ${what}: the brain could not pay for the request, so nothing was sent to GoHighLevel.` : e instanceof Error && e.name === "NotASettingError" ? `Not read, ${what}: it is not an account setting, so the brain did not ask for it.` : `Could not read ${what}: GoHighLevel did not answer.`);
|
|
@@ -35791,20 +35791,20 @@ async function brainAudit(deps, args) {
|
|
|
35791
35791
|
const started = Date.now();
|
|
35792
35792
|
let calls = 0;
|
|
35793
35793
|
const notChecked = [];
|
|
35794
|
-
const read2 = async (what,
|
|
35794
|
+
const read2 = async (what, path67, params) => {
|
|
35795
35795
|
if (calls >= MAX_GHL_CALLS || Date.now() - started > TIME_BUDGET_MS) {
|
|
35796
35796
|
notChecked.push(what);
|
|
35797
35797
|
return { got: "skipped" };
|
|
35798
35798
|
}
|
|
35799
35799
|
calls += 1;
|
|
35800
35800
|
try {
|
|
35801
|
-
return { got: "data", data: await deps.ghlGet(
|
|
35801
|
+
return { got: "data", data: await deps.ghlGet(path67, params) };
|
|
35802
35802
|
} catch {
|
|
35803
35803
|
return { got: "error" };
|
|
35804
35804
|
}
|
|
35805
35805
|
};
|
|
35806
|
-
const ghl = async (what,
|
|
35807
|
-
const r = await read2(what,
|
|
35806
|
+
const ghl = async (what, path67, params) => {
|
|
35807
|
+
const r = await read2(what, path67, params);
|
|
35808
35808
|
if (r.got === "error") notChecked.push(`${what} (GoHighLevel could not be read)`);
|
|
35809
35809
|
return r.got === "data" ? r.data : void 0;
|
|
35810
35810
|
};
|
|
@@ -40647,6 +40647,7 @@ function journalObjectFor(b, parents) {
|
|
|
40647
40647
|
};
|
|
40648
40648
|
const parent = parents.get(b.ref);
|
|
40649
40649
|
if (parent) o.parentRef = parent;
|
|
40650
|
+
if (b.type === "calendar" && b.teamIds?.length) o.teamIds = [...b.teamIds];
|
|
40650
40651
|
return o;
|
|
40651
40652
|
}
|
|
40652
40653
|
function buildRunRecord(input) {
|
|
@@ -40823,6 +40824,32 @@ function describeRuns(locationId2, base) {
|
|
|
40823
40824
|
reverted: reverted.has(r.runId)
|
|
40824
40825
|
})).reverse();
|
|
40825
40826
|
}
|
|
40827
|
+
function calendarTeamsFromJournal(locationId2, base) {
|
|
40828
|
+
const out = {};
|
|
40829
|
+
for (const r of readJournal(locationId2, base)) {
|
|
40830
|
+
if (r.kind !== "build-object") continue;
|
|
40831
|
+
const o = r.object;
|
|
40832
|
+
if (o?.type === "calendar" && o.action === "created" && o.teamIds?.length) out[o.name.trim().toLowerCase()] = [...o.teamIds];
|
|
40833
|
+
}
|
|
40834
|
+
return out;
|
|
40835
|
+
}
|
|
40836
|
+
function madeByBuild(locationId2, planId, base) {
|
|
40837
|
+
const records = readJournal(locationId2, base);
|
|
40838
|
+
const planOf = /* @__PURE__ */ new Map();
|
|
40839
|
+
let since = "";
|
|
40840
|
+
for (const r of records) {
|
|
40841
|
+
if (r.kind === "build-start") planOf.set(r.runId, r.planId);
|
|
40842
|
+
if (r.kind === "revert" && (!planId || planOf.get(r.runId) === planId)) since = r.at > since ? r.at : since;
|
|
40843
|
+
}
|
|
40844
|
+
const out = /* @__PURE__ */ new Map();
|
|
40845
|
+
for (const r of records) {
|
|
40846
|
+
if (r.kind !== "build-object" || r.at <= since) continue;
|
|
40847
|
+
if (planId && planOf.get(r.runId) !== planId) continue;
|
|
40848
|
+
const o = r.object;
|
|
40849
|
+
if (o?.action === "created" && !out.has(o.ref)) out.set(o.ref, { ref: o.ref, type: o.type, name: o.name });
|
|
40850
|
+
}
|
|
40851
|
+
return [...out.values()];
|
|
40852
|
+
}
|
|
40826
40853
|
var fs24, path24, DIRECTORY_FLUSH_UNSUPPORTED;
|
|
40827
40854
|
var init_build_journal = __esm({
|
|
40828
40855
|
"src/build-journal.ts"() {
|
|
@@ -40863,15 +40890,15 @@ function shimTarget(shim) {
|
|
|
40863
40890
|
return null;
|
|
40864
40891
|
}
|
|
40865
40892
|
const refs3 = [...text3.matchAll(/"%~?dp0%?\\([^"\r\n]+)"/g)].map((m) => m[1]);
|
|
40866
|
-
const
|
|
40893
|
+
const resolve15 = (rel) => path25.join(path25.dirname(shim), ...rel.split(/[\\/]/));
|
|
40867
40894
|
const script = refs3.find((r) => /\.(?:js|cjs|mjs)$/i.test(r));
|
|
40868
40895
|
if (script) {
|
|
40869
|
-
const p =
|
|
40896
|
+
const p = resolve15(script);
|
|
40870
40897
|
return fs25.existsSync(p) ? { kind: "script", path: p } : null;
|
|
40871
40898
|
}
|
|
40872
40899
|
const program = refs3.find((r) => /\.exe$/i.test(r) && !/(^|[\\/])node\.exe$/i.test(r));
|
|
40873
40900
|
if (program) {
|
|
40874
|
-
const p =
|
|
40901
|
+
const p = resolve15(program);
|
|
40875
40902
|
return fs25.existsSync(p) ? { kind: "program", path: p } : null;
|
|
40876
40903
|
}
|
|
40877
40904
|
return null;
|
|
@@ -41110,12 +41137,12 @@ function noteObservedEngine(observed, file3 = enginePath()) {
|
|
|
41110
41137
|
return true;
|
|
41111
41138
|
}
|
|
41112
41139
|
function runSmoke(bin, timeoutMs = 9e4) {
|
|
41113
|
-
return new Promise((
|
|
41140
|
+
return new Promise((resolve15) => {
|
|
41114
41141
|
let settled = false;
|
|
41115
41142
|
const done = (r) => {
|
|
41116
41143
|
if (!settled) {
|
|
41117
41144
|
settled = true;
|
|
41118
|
-
|
|
41145
|
+
resolve15(r);
|
|
41119
41146
|
}
|
|
41120
41147
|
};
|
|
41121
41148
|
const cmd = claudeCommand(bin, [
|
|
@@ -41331,7 +41358,7 @@ What the saved plan says should exist:
|
|
|
41331
41358
|
${saved.summary}
|
|
41332
41359
|
1. switch_location to ${locationId2} (${locationName}); verify with get_current_location and stop if it is not that location.
|
|
41333
41360
|
2. Call apply_build_plan with useSavedPlan:true and mode:"dry_run" (no plan argument \u2014 the tool loads the saved plan itself). Read the report: same-named objects that already exist are bound, never duplicated.
|
|
41334
|
-
3. Call apply_build_plan with useSavedPlan:true and mode:"execute". Never delete or recreate anything that already exists.
|
|
41361
|
+
3. Call apply_build_plan with useSavedPlan:true and mode:"execute". Never delete or recreate anything that already exists. ${ONE_EXECUTE_RULE}
|
|
41335
41362
|
4. Read back what landed (get_pipelines, list_workflows_full, get_calendars) and compare it, name for name, with the saved plan above. ${BUILD_REPLY_RULE}
|
|
41336
41363
|
5. ${STAFF_NOTIFICATION_RULE} A notification step whose recipient is empty is listed in issues as waiting for a staff member, never left unmentioned.
|
|
41337
41364
|
6. ${MISSING_STEP_RULE} ${STAFF_PAUSE_RULE}
|
|
@@ -41471,19 +41498,19 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41471
41498
|
const spec = opts.spec ?? STAGE_SPECS[stage];
|
|
41472
41499
|
if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
|
|
41473
41500
|
let engineAcct = {};
|
|
41474
|
-
return new Promise((
|
|
41501
|
+
return new Promise((resolve15) => {
|
|
41475
41502
|
void (async () => {
|
|
41476
41503
|
const bin = claudeBin();
|
|
41477
41504
|
if (!opts.preflightServer && !stageServerPinned()) {
|
|
41478
41505
|
const reason3 = "Command OS could not make a fixed copy of its own server beside it (the install folder may be read-only), so the build step was not started. Reinstall GHL Command in a folder you can write to, or ask for help.";
|
|
41479
41506
|
onEvent({ kind: "error", line: reason3 });
|
|
41480
|
-
|
|
41507
|
+
resolve15({ ok: false, error: reason3 });
|
|
41481
41508
|
return;
|
|
41482
41509
|
}
|
|
41483
41510
|
const preflight = await (opts.preflightServer ?? preflightPinnedServer)();
|
|
41484
41511
|
if (!preflight.ok) {
|
|
41485
41512
|
onEvent({ kind: "error", line: preflight.reason });
|
|
41486
|
-
|
|
41513
|
+
resolve15({ ok: false, error: preflight.reason });
|
|
41487
41514
|
return;
|
|
41488
41515
|
}
|
|
41489
41516
|
onEvent({ kind: "status", line: `Writing through GHL Command build ${ownBuildId()} from ${pinnedMcpConfig().mcpServers.ghl.args[0]}.` });
|
|
@@ -41491,7 +41518,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41491
41518
|
engineAcct = { engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked };
|
|
41492
41519
|
if (!engineCheck.ok) {
|
|
41493
41520
|
onEvent({ kind: "error", line: engineCheck.reason });
|
|
41494
|
-
|
|
41521
|
+
resolve15({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
|
|
41495
41522
|
return;
|
|
41496
41523
|
}
|
|
41497
41524
|
onEvent({ kind: "status", line: `Starting ${spec.name} for ${locationName}.` });
|
|
@@ -41507,7 +41534,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41507
41534
|
if (!opts.preflightServer && buildIdOf(pinned2) !== ownBuildId()) {
|
|
41508
41535
|
const reason3 = `The build step's GHL Command server file changed after it was checked (it is now build ${buildIdOf(pinned2)}, this cockpit is build ${ownBuildId()}), so nothing was started. Restart the cockpit, then try again.`;
|
|
41509
41536
|
onEvent({ kind: "error", line: reason3 });
|
|
41510
|
-
|
|
41537
|
+
resolve15({ ok: false, error: reason3, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked });
|
|
41511
41538
|
return;
|
|
41512
41539
|
}
|
|
41513
41540
|
const cmd = claudeCommand(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context));
|
|
@@ -41604,7 +41631,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41604
41631
|
if (inventoryError) {
|
|
41605
41632
|
if (progressPoll) clearInterval(progressPoll);
|
|
41606
41633
|
onEvent({ kind: "error", line: inventoryError });
|
|
41607
|
-
|
|
41634
|
+
resolve15({ ok: false, error: inventoryError, engineVersion, engineModel, costUsd, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked });
|
|
41608
41635
|
return;
|
|
41609
41636
|
}
|
|
41610
41637
|
if (progressPoll) clearInterval(progressPoll);
|
|
@@ -41625,7 +41652,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41625
41652
|
if (!timedOut && code !== 0 && texts.empty) {
|
|
41626
41653
|
const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}`, ...engine };
|
|
41627
41654
|
onEvent({ kind: "error", line: outcome2.error });
|
|
41628
|
-
|
|
41655
|
+
resolve15(outcome2);
|
|
41629
41656
|
return;
|
|
41630
41657
|
}
|
|
41631
41658
|
const outcome = timedOut ? { ok: false, error: "the run timed out and was stopped before it finished", ...engine } : texts.failure ? { ok: false, error: `the run did not finish (${texts.failure})`, raw: texts.pick().slice(-400), ...engine } : { ...parseResultLine(texts.pick()), ...engine };
|
|
@@ -41639,7 +41666,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41639
41666
|
const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
|
|
41640
41667
|
const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
|
|
41641
41668
|
onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
|
|
41642
|
-
|
|
41669
|
+
resolve15(outcome);
|
|
41643
41670
|
});
|
|
41644
41671
|
child.on("error", (err) => {
|
|
41645
41672
|
spawnFailed = true;
|
|
@@ -41647,16 +41674,16 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41647
41674
|
if (progressPoll) clearInterval(progressPoll);
|
|
41648
41675
|
const outcome = { ok: false, error: `could not start claude: ${err.message}`, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked };
|
|
41649
41676
|
onEvent({ kind: "error", line: outcome.error });
|
|
41650
|
-
|
|
41677
|
+
resolve15(outcome);
|
|
41651
41678
|
});
|
|
41652
41679
|
})().catch((err) => {
|
|
41653
41680
|
const line4 = err instanceof Error ? err.message : String(err);
|
|
41654
41681
|
onEvent({ kind: "error", line: line4 });
|
|
41655
|
-
|
|
41682
|
+
resolve15({ ok: false, error: line4, ...engineAcct });
|
|
41656
41683
|
});
|
|
41657
41684
|
});
|
|
41658
41685
|
}
|
|
41659
|
-
var import_child_process3, fs27, BRIEF_NEEDS_INTAKE, STAFF_NOTIFICATION_RULE, STAFF_PAUSE_RULE, BUILD_REPLY_RULE, MISSING_STEP_RULE, CONFIRM_PAUSE_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES, RESULT_LAST_RULE;
|
|
41686
|
+
var import_child_process3, fs27, BRIEF_NEEDS_INTAKE, ONE_EXECUTE_RULE, STAFF_NOTIFICATION_RULE, STAFF_PAUSE_RULE, BUILD_REPLY_RULE, MISSING_STEP_RULE, CONFIRM_PAUSE_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES, RESULT_LAST_RULE;
|
|
41660
41687
|
var init_stage_runner = __esm({
|
|
41661
41688
|
"src/stage-runner.ts"() {
|
|
41662
41689
|
"use strict";
|
|
@@ -41679,6 +41706,7 @@ var init_stage_runner = __esm({
|
|
|
41679
41706
|
init_client_record();
|
|
41680
41707
|
init_workflow_builder_client();
|
|
41681
41708
|
BRIEF_NEEDS_INTAKE = "First complete the intake. It fills most answers from a call or your notes.";
|
|
41709
|
+
ONE_EXECUTE_RULE = "Make ONE execute call and wait for its answer, however long it takes; never call apply_build_plan again while an earlier call has not answered. If it answers buildRunning, another build is writing to this account: do not call it again; finish with RESULT ok:false naming that.";
|
|
41682
41710
|
STAFF_NOTIFICATION_RULE = `Staff notifications ALWAYS stay in the workflow. Call get_users on THIS account and use only a user id it returns as the recipient. If get_users returns no users, leave the recipient EMPTY ("") \u2014 never use a user id from another account, from the agency, from an earlier conversation or from memory (a foreign id looks like it worked and notifies nobody). An empty recipient can make publish fail: then keep the workflow as a draft with the notification step still in it, and list that step in the RESULT issues as "waiting for a staff member: add one in ${GHL_ADD_STAFF_CLICK_PATH}, then pick them on the notification step and publish".`;
|
|
41683
41711
|
STAFF_PAUSE_RULE = `If apply_build_plan answers with staffPause, copy its reason into issues word for word and do not call those calendars built.`;
|
|
41684
41712
|
BUILD_REPLY_RULE = `A long apply_build_plan reply comes back shortened, with replyTrimmed on it; it still holds everything your report needs (summary, missingSteps, workflowsNotBuilt, staffPause, calendars, countsByType, notClean, templates, nextManualSteps, customValues and, on a dry run, composed.copySlots), so report from it. If you need a section it lists under sectionsNotShownInFull, read it with apply_build_plan detail:{of:"dry_run" or "execute",section:"<name>"}; that call builds nothing.`;
|
|
@@ -41818,7 +41846,7 @@ Use these authoritative normalized intake answers when provided: ${JSON.stringif
|
|
|
41818
41846
|
3. Call apply_build_plan with fromPreset:true, preset:"${presetForIndustry(ctx?.industry)}", brief:<the Brief from step 2>, mode:"dry_run". The server expands the industry preset with the client's staff, calendars (appointment lengths included), pipeline stages, offer and notification rules in under a second, validates it, and reports what would be built.
|
|
41819
41847
|
${BUILD_REPLY_RULE}
|
|
41820
41848
|
4. Read the dry-run report. Its composed.copySlots list the message templates; their copy is already filled from the brief and send-ready. ONLY where the brief gives client-specific material the preset copy misses (the client's signature line, a named offer or price), pass copy:[{ref:"email_template.<slug>", subject, html}] / [{ref:"sms_template.<slug>", body}] for JUST those templates on the next call. Never restructure; never rewrite every message.
|
|
41821
|
-
5. Call apply_build_plan again with the SAME fromPreset:true, preset and brief (plus your copy overrides, if any) and mode:"execute". Never delete or recreate anything that already exists.
|
|
41849
|
+
5. Call apply_build_plan again with the SAME fromPreset:true, preset and brief (plus your copy overrides, if any) and mode:"execute". Never delete or recreate anything that already exists. ${ONE_EXECUTE_RULE}
|
|
41822
41850
|
6. FALLBACK \u2014 only if apply_build_plan answered with phase:"compose" and errors that point at a genuine preset gap (not a fixable brief problem): author the plan yourself per the PLAN GUIDE below, check it with validate_build_plan, STOP if it does not validate, then apply_build_plan dry_run \u2192 execute with that plan.
|
|
41823
41851
|
7. Read back what landed (get_pipelines, list_workflows_full, get_calendars).
|
|
41824
41852
|
8. ${STAFF_NOTIFICATION_RULE} In a hand-authored fallback plan, a notification's "to" is that user id, or "" when the account has no users.
|
|
@@ -42911,13 +42939,13 @@ var init_handoff_pack = __esm({
|
|
|
42911
42939
|
|
|
42912
42940
|
// src/command-os/handoff-run.ts
|
|
42913
42941
|
function askNoTools(prompt, opts = {}) {
|
|
42914
|
-
return new Promise((
|
|
42942
|
+
return new Promise((resolve15) => {
|
|
42915
42943
|
let bin;
|
|
42916
42944
|
try {
|
|
42917
42945
|
bin = claudeBin();
|
|
42918
42946
|
} catch {
|
|
42919
42947
|
opts.onCost?.();
|
|
42920
|
-
|
|
42948
|
+
resolve15("");
|
|
42921
42949
|
return;
|
|
42922
42950
|
}
|
|
42923
42951
|
const cmd = claudeCommand(bin, ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", String(opts.maxTurns ?? 3), "--allowedTools", "", "--disallowedTools", `${NO_TOOLS_DENY},${MCP_DENY}`, ...NO_MEMBER_HOOKS, ...NO_MCP_ARGS]);
|
|
@@ -42951,12 +42979,12 @@ function askNoTools(prompt, opts = {}) {
|
|
|
42951
42979
|
child.on("error", () => {
|
|
42952
42980
|
clearTimeout(timeout);
|
|
42953
42981
|
report3();
|
|
42954
|
-
|
|
42982
|
+
resolve15(texts.pick());
|
|
42955
42983
|
});
|
|
42956
42984
|
child.on("close", () => {
|
|
42957
42985
|
clearTimeout(timeout);
|
|
42958
42986
|
report3();
|
|
42959
|
-
|
|
42987
|
+
resolve15(texts.pick());
|
|
42960
42988
|
});
|
|
42961
42989
|
});
|
|
42962
42990
|
}
|
|
@@ -44650,7 +44678,7 @@ function stopReasonSentence(reason3, fetched, cap2) {
|
|
|
44650
44678
|
}
|
|
44651
44679
|
}
|
|
44652
44680
|
function delay(ms) {
|
|
44653
|
-
return new Promise((
|
|
44681
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
44654
44682
|
}
|
|
44655
44683
|
async function fetchAllContacts(client, locationId2, options = {}) {
|
|
44656
44684
|
const cap2 = options.maxContacts ?? DEFAULT_MAX_CONTACTS;
|
|
@@ -45488,7 +45516,7 @@ var init_client_capabilities = __esm({
|
|
|
45488
45516
|
{ key: "reputation", label: "Reputation and content", blurb: "Reviews, social posts and the blog." },
|
|
45489
45517
|
{ key: "health", label: "Health and handoff", blurb: "Proving it works, keeping it clean, and handing it over." }
|
|
45490
45518
|
];
|
|
45491
|
-
N = (label2,
|
|
45519
|
+
N = (label2, path67, params, key2, nameField, version) => ({ label: label2, path: path67, params, key: key2, nameField, version });
|
|
45492
45520
|
W = (read2, extra) => ({ ...read2, ...extra });
|
|
45493
45521
|
AI2 = "2021-04-15";
|
|
45494
45522
|
CAPABILITIES = [
|
|
@@ -45616,7 +45644,10 @@ var init_client_capabilities = __esm({
|
|
|
45616
45644
|
hub: "command-os/ai-employee.html",
|
|
45617
45645
|
reads: [
|
|
45618
45646
|
W(N("knowledge bases", "/knowledge-bases/", { locationId: "{loc}", limit: "50" }, "knowledgeBases", "name", AI2), { one: "knowledge base" }),
|
|
45619
|
-
N("bots", "/conversation-ai/agents/search", { limit: "50" }, "agents", "name", AI2)
|
|
45647
|
+
N("bots", "/conversation-ai/agents/search", { limit: "50" }, "agents", "name", AI2),
|
|
45648
|
+
// Walk W42: Ready to answer reads the account live, the way Verify does.
|
|
45649
|
+
{ ...N("booking calendars", "/calendars/", { locationId: "{loc}" }, "calendars", "name"), activeOnly: true },
|
|
45650
|
+
N("phone numbers", "/phone-system/numbers/", { locationId: "{loc}" }, "phoneNumbers", "title")
|
|
45620
45651
|
]
|
|
45621
45652
|
},
|
|
45622
45653
|
{
|
|
@@ -45626,7 +45657,7 @@ var init_client_capabilities = __esm({
|
|
|
45626
45657
|
what: "GoHighLevel's name for the agent that answers the phone. Built without a number, given one on purpose, proven by the transcript.",
|
|
45627
45658
|
says: "Check whether a call can land on {client}'s account, then build a Voice AI agent that answers, books and takes a message. Leave it not yet connected to a phone number or calendar until I approve those connections. List a test conversation as still needed.",
|
|
45628
45659
|
hub: "modules/11-your-ai-employee/lesson-05-the-phone-agent.html",
|
|
45629
|
-
reads: [N("phone agents", "/voice-ai/agents", { locationId: "{loc}", page: "1", pageSize: "50" }, "agents", "agentName", AI2), N("phone numbers", "/phone-system/numbers/", { locationId: "{loc}" }, "phoneNumbers", "title")]
|
|
45660
|
+
reads: [N("phone agents", "/voice-ai/agents", { locationId: "{loc}", page: "1", pageSize: "50" }, "agents", "agentName", AI2), N("phone numbers", "/phone-system/numbers/", { locationId: "{loc}" }, "phoneNumbers", "title"), { ...N("booking calendars", "/calendars/", { locationId: "{loc}" }, "calendars", "name"), activeOnly: true }]
|
|
45630
45661
|
},
|
|
45631
45662
|
{
|
|
45632
45663
|
key: "demo",
|
|
@@ -45778,7 +45809,7 @@ var require_package = __commonJS({
|
|
|
45778
45809
|
"package.json"(exports2, module2) {
|
|
45779
45810
|
module2.exports = {
|
|
45780
45811
|
name: "@elitedcs/ghl-mcp",
|
|
45781
|
-
version: "3.93.
|
|
45812
|
+
version: "3.93.1",
|
|
45782
45813
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
45783
45814
|
description: "GoHighLevel MCP Server for Claude. 250 tools for CRM, automation and marketing, an account-wide workflow audit, live funnel-capture verification and a workflow builder, across client accounts.",
|
|
45784
45815
|
main: "dist/index.js",
|
|
@@ -46135,13 +46166,9 @@ function cockpitMoney(amount,currency){
|
|
|
46135
46166
|
var SAY=" (opens in a new tab)";
|
|
46136
46167
|
function mark(a){if(a.getAttribute("data-nt")||a.hidden)return;a.setAttribute("data-nt","1");if((a.textContent||"").indexOf("opens in a new tab")<0){var s=document.createElement("span");s.className="nt";s.textContent=SAY;a.appendChild(s)}}
|
|
46137
46168
|
function markAll(root){var list=(root||document).querySelectorAll?((root||document).querySelectorAll('a[target="_blank"]')):[];for(var i=0;i<list.length;i++)mark(list[i])}
|
|
46138
|
-
|
|
46139
|
-
|
|
46140
|
-
|
|
46141
|
-
if(a.protocol!=="http:"&&a.protocol!=="https:")return;
|
|
46142
|
-
/* "noopener" in the features makes window.open answer null even when it worked, so the opener is cut by hand. */
|
|
46143
|
-
e.preventDefault();var w=null;try{w=window.open(a.href,"_blank")}catch(x){w=null}
|
|
46144
|
-
if(w){try{w.opener=null;w.focus()}catch(x){}}else{var same=document.createElement("a");same.href=a.href;same.click()}},false);
|
|
46169
|
+
/* Walk W34 (2026-10-29, Safari): opening the page by hand inside the click fell back to this tab when Safari refused,
|
|
46170
|
+
and the cockpit was lost. The link is left to the browser: a real click on a target=_blank link opens a new tab in
|
|
46171
|
+
every browser. The redraw hold (clickInFlight) keeps the link under the pointer from being swapped mid-click. */
|
|
46145
46172
|
markAll(document);
|
|
46146
46173
|
try{new MutationObserver(function(ms){for(var i=0;i<ms.length;i++){var n=ms[i].addedNodes;for(var j=0;j<n.length;j++){var x=n[j];if(x.nodeType!==1)continue;if(x.matches&&x.matches('a[target="_blank"]'))mark(x);markAll(x)}}}).observe(document.documentElement,{childList:true,subtree:true})}catch(e){}
|
|
46147
46174
|
})();`;
|
|
@@ -47161,9 +47188,9 @@ var init_cockpit_cache = __esm({
|
|
|
47161
47188
|
*/
|
|
47162
47189
|
readNow(locationId2, page2, opts, priority = 1) {
|
|
47163
47190
|
const key2 = this.cache.key(locationId2, page2);
|
|
47164
|
-
const p = new Promise((
|
|
47191
|
+
const p = new Promise((resolve15) => {
|
|
47165
47192
|
const w = this.waiters.get(key2) ?? [];
|
|
47166
|
-
w.push(
|
|
47193
|
+
w.push(resolve15);
|
|
47167
47194
|
this.waiters.set(key2, w);
|
|
47168
47195
|
});
|
|
47169
47196
|
this.queue(locationId2, page2, priority, opts);
|
|
@@ -47187,7 +47214,7 @@ var init_cockpit_cache = __esm({
|
|
|
47187
47214
|
const key2 = this.cache.key(locationId2, page2);
|
|
47188
47215
|
const ticket = given ?? this.cache.ticket(locationId2, page2);
|
|
47189
47216
|
return new Promise((done) => {
|
|
47190
|
-
const
|
|
47217
|
+
const resolve15 = (v) => {
|
|
47191
47218
|
setTimeout(() => done(v.ok && !this.cache.stillValid(ticket) ? { ok: false, error: new StaleReadError() } : v.ok ? { ...v, ticket } : v), 0);
|
|
47192
47219
|
};
|
|
47193
47220
|
this.pacer.enqueue(`${key2}#live${++this.liveSeq}`, locationId2 === "agency" ? "agency" : locationId2, priority, 0, (gate3) => this.cache.inRegion(ticket, async () => {
|
|
@@ -47198,17 +47225,17 @@ var init_cockpit_cache = __esm({
|
|
|
47198
47225
|
const data = await Promise.race([opts.read((client) => pacedClient(client, gate3, count4), (fn) => !late && !gate3.expired?.() && this.cache.commitIfCurrent(ticket, fn)), gate3.deadline ?? NEVER]);
|
|
47199
47226
|
const kept = this.cache.putFor(ticket, data, { reach: opts.reach, calls: count4.calls, ghlMs: this.now() - started, holdSave: opts.holdSave });
|
|
47200
47227
|
if (kept === null) {
|
|
47201
|
-
|
|
47228
|
+
resolve15({ ok: false, error: new StaleReadError() });
|
|
47202
47229
|
return;
|
|
47203
47230
|
}
|
|
47204
47231
|
if (kept.changed) opts.onChange?.(data);
|
|
47205
|
-
|
|
47232
|
+
resolve15({ ok: true, data });
|
|
47206
47233
|
} catch (e) {
|
|
47207
47234
|
if (e instanceof PacerDeadlineError) late = true;
|
|
47208
47235
|
this.cache.failFor(ticket, e instanceof PacerCeilingError ? DAILY_CEILING_TEXT : e instanceof PacerDeadlineError ? DEADLINE_TEXT : isSlowDown(e) ? SLOW_DOWN_TEXT : opts.failureText?.(e) ?? "could not read: try Refresh");
|
|
47209
|
-
|
|
47236
|
+
resolve15({ ok: false, error: e });
|
|
47210
47237
|
}
|
|
47211
|
-
}), () =>
|
|
47238
|
+
}), () => resolve15({ ok: false, error: new StaleReadError() }));
|
|
47212
47239
|
});
|
|
47213
47240
|
}
|
|
47214
47241
|
queue(locationId2, page2, priority, opts) {
|
|
@@ -47807,8 +47834,8 @@ function scoreRun(run) {
|
|
|
47807
47834
|
const drafts = run.draftWorkflows ?? [];
|
|
47808
47835
|
const stepProblems = st?.read ? (st.problems ?? []).length : 0;
|
|
47809
47836
|
if (f.sent === 0 && judgedSteps === 0 && drafts.length === 0 && stepProblems === 0) return { graded: false, reason: "Nothing the test lead received and no workflow step could be judged, so there is nothing to grade. Check that the workflows are published, then run it again." };
|
|
47810
|
-
const
|
|
47811
|
-
if (
|
|
47837
|
+
const held2 = (f.notYetDue ?? []).filter((m) => m.heldByWait).length;
|
|
47838
|
+
if (held2) return { graded: false, reason: `GoHighLevel was still holding the test lead, so ${held2} planned message${held2 === 1 ? " was" : "s were"} not judged. A grade now would look better than the truth. Run the check again.` };
|
|
47812
47839
|
const deductions2 = [];
|
|
47813
47840
|
const add2 = (kind, area, text3) => {
|
|
47814
47841
|
const p = POINTS[kind];
|
|
@@ -48174,7 +48201,7 @@ async function listLeadCarriers(client, run) {
|
|
|
48174
48201
|
const carriers = /* @__PURE__ */ new Map();
|
|
48175
48202
|
let partial = false;
|
|
48176
48203
|
try {
|
|
48177
|
-
const paged = await readContactPages((
|
|
48204
|
+
const paged = await readContactPages((path67, o) => client.get(path67, o), { locationId: run.locationId, query: run.leadEmail, limit: 20 });
|
|
48178
48205
|
if (!paged || !paged.complete) partial = true;
|
|
48179
48206
|
for (const c of paged?.rows ?? []) {
|
|
48180
48207
|
const k = carrierOf(c);
|
|
@@ -48231,23 +48258,23 @@ function claimRun(locationId2, runId, base) {
|
|
|
48231
48258
|
};
|
|
48232
48259
|
} catch (e) {
|
|
48233
48260
|
if (e.code !== "EEXIST") throw e;
|
|
48234
|
-
let
|
|
48261
|
+
let held2 = "";
|
|
48235
48262
|
let age = 0;
|
|
48236
48263
|
try {
|
|
48237
|
-
|
|
48264
|
+
held2 = import_node_fs7.default.readFileSync(lock, "utf8");
|
|
48238
48265
|
age = Date.now() - import_node_fs7.default.statSync(lock).mtimeMs;
|
|
48239
48266
|
} catch {
|
|
48240
48267
|
continue;
|
|
48241
48268
|
}
|
|
48242
48269
|
let pid = 0;
|
|
48243
48270
|
try {
|
|
48244
|
-
pid = Number(JSON.parse(
|
|
48271
|
+
pid = Number(JSON.parse(held2).pid);
|
|
48245
48272
|
} catch {
|
|
48246
48273
|
}
|
|
48247
48274
|
const stale = age > 10 * 6e4 || pid > 0 && !pidAlive(pid);
|
|
48248
48275
|
if (!stale) return null;
|
|
48249
48276
|
try {
|
|
48250
|
-
if (import_node_fs7.default.readFileSync(lock, "utf8") ===
|
|
48277
|
+
if (import_node_fs7.default.readFileSync(lock, "utf8") === held2) import_node_fs7.default.unlinkSync(lock);
|
|
48251
48278
|
} catch {
|
|
48252
48279
|
}
|
|
48253
48280
|
}
|
|
@@ -48284,10 +48311,10 @@ async function finishShopper(locationId2, runId, deps) {
|
|
|
48284
48311
|
if (deps.stepLog && run.entry.kind === "workflows") {
|
|
48285
48312
|
run.steps = await auditSteps(deps.stepLog, run.entry.workflowIds, run.workflowNames ?? {}, run.contactId, run.startedAt, t, { hasPhone: Boolean(run.testPhone), hasEmail: Boolean(run.leadEmail), ...run.staffGaps?.length ? { staffGaps: run.staffGaps } : {} });
|
|
48286
48313
|
}
|
|
48287
|
-
const
|
|
48314
|
+
const held2 = closed && run.steps?.read ? heldWaits(run.steps.rows) : [];
|
|
48288
48315
|
const base = run.originalClosesAt ?? run.closesAt;
|
|
48289
48316
|
const cap2 = base + MAX_EXTEND_MINUTES * 6e4;
|
|
48290
|
-
const soon =
|
|
48317
|
+
const soon = held2.filter((h) => h.waitUntil !== void 0 && h.waitUntil <= t + 5 * 6e4);
|
|
48291
48318
|
if (soon.length && t < cap2) {
|
|
48292
48319
|
run.originalClosesAt = base;
|
|
48293
48320
|
run.closesAt = Math.min(cap2, Math.max(t, ...soon.map((h) => h.waitUntil)) + 3 * 6e4);
|
|
@@ -48295,7 +48322,7 @@ async function finishShopper(locationId2, runId, deps) {
|
|
|
48295
48322
|
saveRun(run, deps.base);
|
|
48296
48323
|
return run;
|
|
48297
48324
|
}
|
|
48298
|
-
const late =
|
|
48325
|
+
const late = held2.filter((h) => h.waitUntil !== void 0 && h.waitUntil <= t);
|
|
48299
48326
|
if (closed && late.length) {
|
|
48300
48327
|
const rows2 = run.steps.rows;
|
|
48301
48328
|
const stillHeld = run.findings.missing.filter((m) => m.workflowId && m.stepId && late.some((h) => h.workflowId === m.workflowId && h.stepId !== void 0 && (m.afterWaits ?? []).includes(h.stepId)) && !rows2.some((r) => r.workflowId === m.workflowId && r.stepId === m.stepId));
|
|
@@ -48462,7 +48489,6 @@ function planCopy(src, opts) {
|
|
|
48462
48489
|
const nodes = src.templates.map(asObj9);
|
|
48463
48490
|
if (nodes.length === 0) return { ok: false, refusal: `"${src.name}" has no steps to test.` };
|
|
48464
48491
|
if (nodes.length > 40) return { ok: false, refusal: `"${src.name}" has ${nodes.length} steps; fast-forward copies at most 40.` };
|
|
48465
|
-
if (str11(src.status)?.toLowerCase() === "draft") return { ok: false, refusal: `"${src.name}" is still a draft, so real leads never go through it. Publish it first, or test it as it is.` };
|
|
48466
48492
|
const idMap = /* @__PURE__ */ new Map();
|
|
48467
48493
|
for (const n of nodes) {
|
|
48468
48494
|
const id2 = str11(n.id);
|
|
@@ -48861,17 +48887,17 @@ ${ringHtml(card.score, card.coverageLabel ?? card.coverage, true)}</div>`;
|
|
|
48861
48887
|
}
|
|
48862
48888
|
const minutesLeft = Math.max(0, Math.ceil((run.closesAt - now) / 6e4));
|
|
48863
48889
|
const planned = run.planNote.source !== "none" && run.expect.length > 0;
|
|
48864
|
-
const
|
|
48865
|
-
const title2 = !f ? "Watching what the test lead receives" : f.sent === 0 ? !watching && nothingArrivedHeading(run.steps) || `Nothing arrived in ${watching ? "the time so far" : `${run.watchMinutes} minutes`}` : f.problems.length || f.missing.length ? "What a new lead actually received" :
|
|
48890
|
+
const held2 = f?.notYetDue.filter((m) => m.heldByWait) ?? [];
|
|
48891
|
+
const title2 = !f ? "Watching what the test lead receives" : f.sent === 0 ? !watching && nothingArrivedHeading(run.steps) || `Nothing arrived in ${watching ? "the time so far" : `${run.watchMinutes} minutes`}` : f.problems.length || f.missing.length ? "What a new lead actually received" : held2.length ? "Not finished: GoHighLevel was still holding the test lead" : planned ? "A new lead got exactly what you planned" : "No problems in what the lead received";
|
|
48866
48892
|
const head4 = pageHead(title2, `${productName()} made a test lead, then read every text and email this account sent it, word for word. ${esc2(run.entryNote)}`, action);
|
|
48867
48893
|
const worst = f?.problems[0];
|
|
48868
48894
|
const quote2 = (tone, text3, cite) => `<div class="cds-callout quote${tone ? ` ${tone}` : ""}">${text3}<cite>${cite}</cite></div>`;
|
|
48869
|
-
const quoteHtml = f && f.sent === 0 ? quote2("warn", "The test lead received no text and no email.", esc2(nothingArrivedWhy(run.steps, run.draftWorkflows))) : worst ? quote2("bad", `${worst.kind === "blank_field" ? "A new lead with no first name on file got" : "A new lead got"} "${marked(worst.quote, "")}"`, `${esc2(worst.title)}${f && f.problems.length > 1 ? `. The first of ${f.problems.length}.` : "."}`) : f &&
|
|
48895
|
+
const quoteHtml = f && f.sent === 0 ? quote2("warn", "The test lead received no text and no email.", esc2(nothingArrivedWhy(run.steps, run.draftWorkflows))) : worst ? quote2("bad", `${worst.kind === "blank_field" ? "A new lead with no first name on file got" : "A new lead got"} "${marked(worst.quote, "")}"`, `${esc2(worst.title)}${f && f.problems.length > 1 ? `. The first of ${f.problems.length}.` : "."}`) : f && held2.length && !f.missing.length ? quote2("warn", `${held2.length} planned message${held2.length === 1 ? " was" : "s were"} still waiting behind a wait GoHighLevel had not let go of.`, "Not judged. Run the check again.") : f && f.missing.length ? quote2("warn", `${f.missing.length} planned message${f.missing.length === 1 ? " never" : "s never"} arrived.`, "Everything that did arrive read correctly.") : f ? quote2("", `Every message read correctly${planned ? " and arrived when planned" : ""}.`, `Checked for the first ${run.watchMinutes} minutes. Later messages are listed below as not yet checked.`) : "";
|
|
48870
48896
|
const stats = f ? `<div class="stats">
|
|
48871
48897
|
${statTile(f.sent, "Messages sent")}
|
|
48872
48898
|
${statTile(f.problems.length, "A customer would notice", f.problems.length ? "bad" : "")}
|
|
48873
48899
|
${run.steps?.read ? statTile(run.steps.rows.length ? run.steps.problems.length : "\u2013", "Steps failed, skipped or did nothing", run.steps.problems.length ? "bad" : run.steps.rows.length && !run.steps.unreadWorkflows?.length && !run.steps.partialWorkflows?.length && !run.steps.counts.unclear ? "" : "warn") : ""}
|
|
48874
|
-
${statTile(run.planNote.source === "none" ? "\u2013" : f.missing.length, "Planned, never sent", f.missing.length ||
|
|
48900
|
+
${statTile(run.planNote.source === "none" ? "\u2013" : f.missing.length, "Planned, never sent", f.missing.length || held2.length ? "warn" : "")}
|
|
48875
48901
|
${(() => {
|
|
48876
48902
|
const opened = f.links.total - f.links.skipped;
|
|
48877
48903
|
return opened ? statTile(`${f.links.ok} of ${opened}`, `Links open${f.links.skipped ? ` \xB7 ${f.links.skipped} not opened` : ""}`, f.links.broken ? "bad" : "") : statTile(0, `Links opened${f.links.skipped ? ` \xB7 ${f.links.skipped} not` : ""}`);
|
|
@@ -48949,7 +48975,7 @@ function startForm(d, inlineSubmit) {
|
|
|
48949
48975
|
<div class="cds-field" id="alert-to-wrap" hidden><label for="alert-to">Staff alerts, tasks and assignments go to</label><div class="hint" id="alert-to-hint">Instead of your staff, marked TEST.</div><select class="cds-select" id="alert-to" name="alertUserId" aria-describedby="alert-to-hint"></select></div>
|
|
48950
48976
|
<fieldset class="cds-field" id="hooks" hidden><legend class="lbl">Outside services these workflows send the lead to</legend><div class="wf" id="hooks-list"></div></fieldset>
|
|
48951
48977
|
${callout("warn", "", "The test lead has no first name, on purpose. The workflows you tick will really send to your address and may alert your staff once.")}
|
|
48952
|
-
|
|
48978
|
+
<div class="cds-row"><button class="cds-btn${inlineSubmit ? " ghost" : ""}" type="submit" id="start-btn" data-start="" disabled>Start the check</button></div>
|
|
48953
48979
|
<div id="start-ask"></div>
|
|
48954
48980
|
<p class="cds-callout result" id="start-result" role="status" aria-live="polite"></p>
|
|
48955
48981
|
</form>`;
|
|
@@ -48960,7 +48986,7 @@ function shopperPageHtml(d, seatToken, seatHeader) {
|
|
|
48960
48986
|
const run = d.run;
|
|
48961
48987
|
const graded2 = !!(run && run.status === "done" && run.findings && scoreRun(run).graded);
|
|
48962
48988
|
const canStart = !!d.client && run?.status !== "watching";
|
|
48963
|
-
const action = graded2 && run ? `<a class="cds-btn" href="${reportHref(run)}" target="_blank" rel="noopener">Open the report</a>` : canStart ? `<button class="cds-btn" type="submit" form="start-form" id="start-btn" disabled>Start the check</button>` : "";
|
|
48989
|
+
const action = graded2 && run ? `<a class="cds-btn" href="${reportHref(run)}" target="_blank" rel="noopener">Open the report</a>` : canStart ? `<button class="cds-btn" type="submit" form="start-form" id="start-btn-top" data-start="" disabled>Start the check</button>` : "";
|
|
48964
48990
|
let body;
|
|
48965
48991
|
if (!d.client) {
|
|
48966
48992
|
body = pageHead("See what a new lead actually receives", `Pick a client account. ${productName()} makes a test lead with your own email address, puts it through the workflows you choose, then reads every text and email the account sent it, word for word, and tells you what a real customer would have seen wrong.`) + picker;
|
|
@@ -48986,7 +49012,7 @@ ${COCKPIT_TYPE_JS}
|
|
|
48986
49012
|
function drawWorkflows(ws,keep){shownWorkflows=ws.map(function(w){return {id:w.id,name:w.name,status:w.status}});
|
|
48987
49013
|
if(!ws.length){list.innerHTML='<p class="err">This account has no workflows to test.</p>';return}
|
|
48988
49014
|
list.innerHTML=ws.map(function(w){return '<label class="cds-check"><input type="checkbox" name="wf" value="'+esc(w.id)+'"'+(keep&&keep[w.id]?' checked':'')+'><span>'+esc(w.name)+(w.status&&w.status!=="published"?' <span class="cds-chip warn">'+esc(w.status)+'</span>':'')+'</span></label>'}).join("");
|
|
48989
|
-
|
|
49015
|
+
[].forEach.call(document.querySelectorAll("[data-start]"),function(b){b.disabled=false})}
|
|
48990
49016
|
// The cockpit keeps what it has loaded (cockpit-cache.ts): the list says how old it is, with one Refresh; a stale
|
|
48991
49017
|
// list is re-read behind the page, and a look that found nothing new never redraws it.
|
|
48992
49018
|
${AGE_LINE_JS}
|
|
@@ -49015,12 +49041,30 @@ ${COCKPIT_TYPE_JS}
|
|
|
49015
49041
|
if(run&&run.status==="watching"&&run.due){fetch("/api/shopper/finish",{method:"POST",headers:hdrs({"Content-Type":"application/json"}),body:JSON.stringify({client:client,runId:run.runId})}).then(function(r){return r.json()}).then(function(r){if(r.ok)location.reload()})}
|
|
49016
49042
|
function post(path, body){return fetch(path,{method:"POST",headers:hdrs({"Content-Type":"application/json"}),body:JSON.stringify(body)}).then(function(r){return r.json()})}
|
|
49017
49043
|
var form=document.getElementById("start-form"), startConfirmed=false;
|
|
49044
|
+
function startBody(ids,hosts){return {client:client,workflowIds:ids,ownerEmail:document.getElementById("owner-email").value,testPhone:document.getElementById("test-phone").value||undefined,watchMinutes:(document.getElementById("watch").value==="auto"?undefined:Number(document.getElementById("watch").value)),fastForward:document.getElementById("ff").checked,allowedHosts:hosts,alertUserId:(document.getElementById("alert-to").value||undefined),workflowsShown:shownWorkflows}}
|
|
49045
|
+
/* Walk W39: the panel asks the start's own checks first (preview, nothing written) and never promises a start the
|
|
49046
|
+
server would refuse: a refusal is shown with no confirm button. */
|
|
49018
49047
|
function showStartAsk(ids){
|
|
49048
|
+
var ask=document.getElementById("start-ask");
|
|
49049
|
+
var hosts=[].slice.call(form.querySelectorAll('input[name="hook"]:checked')).map(function(i){return i.value});
|
|
49050
|
+
ask.innerHTML='<div class="cds-callout"><span class="ttl">Checking the workflows\u2026</span>Nothing is written yet.</div>';
|
|
49051
|
+
var body=startBody(ids,hosts);body.preview=true;
|
|
49052
|
+
post("/api/shopper/start",body).then(function(r){
|
|
49053
|
+
if(!r||!r.ok){ask.innerHTML='<div class="cds-callout warn"><span class="ttl">This check cannot start</span>'+esc((r&&r.error)||"The workflows could not be checked.")+'</div>';if(r&&r.stale&&r.workflows){var keep={};ids.forEach(function(id){keep[id]=true});drawWorkflows(r.workflows,keep)}return}
|
|
49054
|
+
if(r.refusals&&r.refusals.length){ask.innerHTML='<div class="cds-callout warn"><span class="ttl">This check cannot start as it is</span><ul>'+r.refusals.map(function(x){return '<li>'+esc(x)+'</li>'}).join("")+'</ul>Nothing was written.</div>';return}
|
|
49055
|
+
showStartPanel(ids)})
|
|
49056
|
+
.catch(function(){ask.innerHTML='<div class="cds-callout warn"><span class="ttl">This check cannot start</span>${productName()} is not answering on this computer.</div>'})}
|
|
49057
|
+
function showStartPanel(ids){
|
|
49019
49058
|
var names=ids.map(function(id){var w=(shownWorkflows||[]).filter(function(x){return x.id===id})[0];return w?w.name:id});
|
|
49020
49059
|
var ff=document.getElementById("ff").checked, email=document.getElementById("owner-email").value, phone=document.getElementById("test-phone").value;
|
|
49021
49060
|
var li=['A test contact named "Secret Shopper" with your email ('+esc(email)+')'+(phone?' and the phone '+esc(phone):'')+', and the tag command-os-secret-shopper. It stays in the account afterwards; delete it when you no longer need it.'];
|
|
49022
49061
|
if(ff)li.push('Temporary copies of '+names.length+' workflow'+(names.length===1?'':'s')+', switched on while the check runs: '+names.map(function(n){return esc("${FF_PREFIX} "+n)}).join(", ")+' (each name also carries a short code). The test lead goes into the copies, not the originals, and the copies are deleted when the check ends.');
|
|
49023
49062
|
else li.push('The test lead goes into '+names.map(esc).join(", ")+'. Those workflows really text and email you, and may alert your staff once.');
|
|
49063
|
+
// Walk W38: in the copies, staff alerts, tasks and assignments go to one person, marked TEST; say who, by name.
|
|
49064
|
+
if(ff){var at=document.getElementById("alert-to"),opt=at&&at.value?at.options[at.selectedIndex]:null,who=opt?String(opt.textContent||"").replace(/\\s*\\([^()]*@[^()]*\\)\\s*$/,""):"";
|
|
49065
|
+
li.push(who?'Staff alerts, tasks and assignments in the copies go to '+esc(who)+', marked TEST.':'Staff alerts, tasks and assignments in the copies go to your own login in this account, if your email matches one, marked TEST.');
|
|
49066
|
+
// Walk W40: the copy's safeguards, said before the click.
|
|
49067
|
+
li.push('The copies have no triggers, so only the test lead goes in and no real lead can enter them. The workflows themselves are not changed, and a draft stays a draft.')}
|
|
49024
49068
|
var ask=document.getElementById("start-ask");
|
|
49025
49069
|
ask.innerHTML='<div class="cds-callout"><span class="ttl">Before the check starts: this writes to '+esc(clientName)+'</span><ul>'+li.map(function(x){return '<li>'+x+'</li>'}).join("")+'</ul><div class="cds-actions"><button class="cds-btn" type="button" id="start-yes">Start the check in '+esc(clientName)+'</button><button class="cds-btn ghost" type="button" id="start-no">Cancel</button></div></div>';
|
|
49026
49070
|
document.getElementById("start-yes").onclick=function(){startConfirmed=true;form.dispatchEvent(new Event("submit",{cancelable:true}))};
|
|
@@ -49039,15 +49083,15 @@ ${COCKPIT_TYPE_JS}
|
|
|
49039
49083
|
// Walk 2026-10-28 W8: before anything is written, say what the check writes, in names, in this account.
|
|
49040
49084
|
if(!startConfirmed){showStartAsk(ids);return}
|
|
49041
49085
|
startConfirmed=false;document.getElementById("start-ask").innerHTML="";
|
|
49042
|
-
var
|
|
49086
|
+
var btns=[].slice.call(document.querySelectorAll("[data-start]"));btns.forEach(function(b){b.disabled=true});say("Starting\u2026");
|
|
49043
49087
|
var hosts=[].slice.call(form.querySelectorAll('input[name="hook"]:checked')).map(function(i){return i.value});
|
|
49044
|
-
post("/api/shopper/start",
|
|
49088
|
+
post("/api/shopper/start",startBody(ids,hosts))
|
|
49045
49089
|
.then(function(r){if(r.ok){location.reload();return}
|
|
49046
49090
|
// Out of date: the list is drawn again as the account has it now, the ticks still there are kept, and the
|
|
49047
49091
|
// refusal says what changed. Nothing started; pressing Start again sends the list now shown.
|
|
49048
49092
|
if(r.stale&&r.workflows){var keep={};ids.forEach(function(id){keep[id]=true});drawWorkflows(r.workflows,keep)}
|
|
49049
|
-
|
|
49050
|
-
.catch(function(){
|
|
49093
|
+
btns.forEach(function(b){b.disabled=false});say(r.error||"That did not start.")})
|
|
49094
|
+
.catch(function(){btns.forEach(function(b){b.disabled=false});say("That did not start. Check that ${productName()} is still running.")});
|
|
49051
49095
|
})}
|
|
49052
49096
|
// Fast-forward sends the test lead to the workflows' outside services for real:
|
|
49053
49097
|
// read them from the chosen workflows and let the owner tick each one.
|
|
@@ -49205,6 +49249,68 @@ function websiteHost(url) {
|
|
|
49205
49249
|
return void 0;
|
|
49206
49250
|
}
|
|
49207
49251
|
}
|
|
49252
|
+
function smallCopyPaths(file3, st) {
|
|
49253
|
+
let dir;
|
|
49254
|
+
try {
|
|
49255
|
+
dir = path32.join(appDataDir(), "logo-copies");
|
|
49256
|
+
} catch {
|
|
49257
|
+
return void 0;
|
|
49258
|
+
}
|
|
49259
|
+
const key2 = crypto11.createHash("sha256").update(`${path32.resolve(file3)}|${st.size}|${st.mtimeMs}`).digest("hex").slice(0, 24);
|
|
49260
|
+
return [{ px: 600, format: "png", mime: "image/png" }, { px: 400, format: "png", mime: "image/png" }, { px: 600, format: "jpeg", mime: "image/jpeg" }].map((t) => ({ ...t, out: path32.join(dir, `${key2}-${t.px}.${t.format === "png" ? "png" : "jpg"}`) }));
|
|
49261
|
+
}
|
|
49262
|
+
function smallLogoCopy(file3, st, maxBytes) {
|
|
49263
|
+
for (const t of smallCopyPaths(file3, st) ?? []) {
|
|
49264
|
+
try {
|
|
49265
|
+
const buf = fs33.readFileSync(t.out);
|
|
49266
|
+
if (buf.length <= maxBytes && RASTER_TYPES.some((r) => r.mime === t.mime && r.test(buf))) return { mime: t.mime, buf };
|
|
49267
|
+
} catch {
|
|
49268
|
+
}
|
|
49269
|
+
}
|
|
49270
|
+
return void 0;
|
|
49271
|
+
}
|
|
49272
|
+
function prepareSmallLogo(file3, maxBytes = LOGO_MAX_BYTES) {
|
|
49273
|
+
if (!file3 || process.platform !== "darwin") return Promise.resolve();
|
|
49274
|
+
const abs = path32.resolve(file3);
|
|
49275
|
+
const running = preparing.get(abs);
|
|
49276
|
+
if (running) return running;
|
|
49277
|
+
const job = (async () => {
|
|
49278
|
+
let st;
|
|
49279
|
+
try {
|
|
49280
|
+
st = fs33.statSync(abs);
|
|
49281
|
+
} catch {
|
|
49282
|
+
return;
|
|
49283
|
+
}
|
|
49284
|
+
if (!st.isFile() || st.size <= maxBytes || st.size > LOGO_SOURCE_MAX_BYTES) return;
|
|
49285
|
+
if (smallLogoCopy(abs, st, maxBytes)) return;
|
|
49286
|
+
const tries = smallCopyPaths(abs, st);
|
|
49287
|
+
if (!tries) return;
|
|
49288
|
+
try {
|
|
49289
|
+
ensureAppDataDir();
|
|
49290
|
+
fs33.mkdirSync(path32.dirname(tries[0].out), { recursive: true, mode: 448 });
|
|
49291
|
+
} catch {
|
|
49292
|
+
return;
|
|
49293
|
+
}
|
|
49294
|
+
for (const t of tries) {
|
|
49295
|
+
const tmp = `${t.out}.tmp-${crypto11.randomUUID()}.${t.format === "png" ? "png" : "jpg"}`;
|
|
49296
|
+
const ok = await new Promise((done) => (0, import_node_child_process3.execFile)("/usr/bin/sips", ["-Z", String(t.px), "-s", "format", t.format, ...t.format === "jpeg" ? ["-s", "formatOptions", "80"] : [], abs, "--out", tmp], { timeout: 2e4 }, (e) => done(!e)));
|
|
49297
|
+
try {
|
|
49298
|
+
if (ok && fs33.statSync(tmp).size <= maxBytes) {
|
|
49299
|
+
fs33.renameSync(tmp, t.out);
|
|
49300
|
+
return;
|
|
49301
|
+
}
|
|
49302
|
+
} catch {
|
|
49303
|
+
}
|
|
49304
|
+
try {
|
|
49305
|
+
fs33.rmSync(tmp, { force: true });
|
|
49306
|
+
} catch {
|
|
49307
|
+
}
|
|
49308
|
+
}
|
|
49309
|
+
notSmallEnough.add(fileKey(abs, st));
|
|
49310
|
+
})().finally(() => preparing.delete(abs));
|
|
49311
|
+
preparing.set(abs, job);
|
|
49312
|
+
return job;
|
|
49313
|
+
}
|
|
49208
49314
|
function logoFileDataUri(file3, maxBytes = LOGO_MAX_BYTES) {
|
|
49209
49315
|
const name2 = path32.basename(file3);
|
|
49210
49316
|
let st;
|
|
@@ -49214,7 +49320,7 @@ function logoFileDataUri(file3, maxBytes = LOGO_MAX_BYTES) {
|
|
|
49214
49320
|
return { problem: `The logo file ${name2} was not found on this computer.` };
|
|
49215
49321
|
}
|
|
49216
49322
|
if (!st.isFile()) return { problem: `The logo path ${name2} is not a file.` };
|
|
49217
|
-
if (st.size >
|
|
49323
|
+
if (st.size > LOGO_SOURCE_MAX_BYTES) return { problem: `The logo file ${name2} is ${Math.round(st.size / 1024 / 1024)} MB, more than the ${LOGO_SOURCE_MAX_BYTES / 1024 / 1024} MB a logo file can be.` };
|
|
49218
49324
|
let buf;
|
|
49219
49325
|
try {
|
|
49220
49326
|
buf = fs33.readFileSync(file3);
|
|
@@ -49222,7 +49328,16 @@ function logoFileDataUri(file3, maxBytes = LOGO_MAX_BYTES) {
|
|
|
49222
49328
|
return { problem: `The logo file ${name2} could not be read.` };
|
|
49223
49329
|
}
|
|
49224
49330
|
const raster = RASTER_TYPES.find((t) => t.test(buf));
|
|
49225
|
-
if (raster) return { src: `data:${raster.mime};base64,${buf.toString("base64")}` };
|
|
49331
|
+
if (raster && buf.length <= maxBytes) return { src: `data:${raster.mime};base64,${buf.toString("base64")}` };
|
|
49332
|
+
if (raster) {
|
|
49333
|
+
const small = smallLogoCopy(file3, st, maxBytes);
|
|
49334
|
+
if (small) return { src: `data:${small.mime};base64,${small.buf.toString("base64")}` };
|
|
49335
|
+
if (process.platform !== "darwin") return { problem: `The logo file ${name2} is large, and making a small copy of it needs a Mac. The logo link from Your agency works instead.` };
|
|
49336
|
+
if (notSmallEnough.has(fileKey(path32.resolve(file3), st))) return { problem: `The logo file ${name2} could not be made small enough for a report. The logo link from Your agency works instead.` };
|
|
49337
|
+
if (!preparing.has(path32.resolve(file3))) void prepareSmallLogo(file3, maxBytes);
|
|
49338
|
+
return { problem: `The small copy of the logo file ${name2} for reports is being made. It shows from the next report; the logo link from Your agency works meanwhile.` };
|
|
49339
|
+
}
|
|
49340
|
+
if (st.size > maxBytes) return { problem: `The logo file ${name2} is not a PNG, JPEG, WebP or GIF image, and is too large to embed as an SVG drawing.` };
|
|
49226
49341
|
const text3 = buf.toString("utf8").replace(/^\ufeff/, "");
|
|
49227
49342
|
if (/<svg[\s>]/i.test(text3)) {
|
|
49228
49343
|
let svg;
|
|
@@ -49359,10 +49474,12 @@ function brandChooserHtml(b, form) {
|
|
|
49359
49474
|
const save = form.save ? `<p class="rp-soon" id="rp-choose-status" role="status" hidden></p><button type="button" class="cds-btn ghost" id="rp-choose-retry" hidden>Try again</button><script>${brandChooserSaveJs(form.save)}</script>` : "";
|
|
49360
49475
|
return `<form class="rp-choose" method="get" action="${esc3(form.action)}"><span>Branding on this report</span>${hidden2}<span class="cds-seg">${opt("agency", !b.agencyAvailable)}${opt("neutral")}${opt("standard")}</span></form>${save}${notes}`;
|
|
49361
49476
|
}
|
|
49362
|
-
var fs33, path32, REPORT_BRAND_MODES, REPORT_BRAND_LABELS, LEGACY_MODES, MIN_ACCENT_CONTRAST, TEXT_BACKGROUNDS, GHL_FOOTER, LOGO_MAX_BYTES, RASTER_TYPES, LOGO_DATA_URI, REPORT_BRAND_CHOICES_ENV, LOCATION_ID, esc3, BRAND_LAYOUT_CSS, BRAND_LOGO_JS, BRAND_SAVE_UNREACHABLE;
|
|
49477
|
+
var crypto11, import_node_child_process3, fs33, path32, REPORT_BRAND_MODES, REPORT_BRAND_LABELS, LEGACY_MODES, MIN_ACCENT_CONTRAST, TEXT_BACKGROUNDS, GHL_FOOTER, LOGO_MAX_BYTES, LOGO_SOURCE_MAX_BYTES, preparing, notSmallEnough, fileKey, RASTER_TYPES, LOGO_DATA_URI, REPORT_BRAND_CHOICES_ENV, LOCATION_ID, esc3, BRAND_LAYOUT_CSS, BRAND_LOGO_JS, BRAND_SAVE_UNREACHABLE;
|
|
49363
49478
|
var init_report_brand = __esm({
|
|
49364
49479
|
"src/command-os/report-brand.ts"() {
|
|
49365
49480
|
"use strict";
|
|
49481
|
+
crypto11 = __toESM(require("node:crypto"));
|
|
49482
|
+
import_node_child_process3 = require("node:child_process");
|
|
49366
49483
|
fs33 = __toESM(require("node:fs"));
|
|
49367
49484
|
path32 = __toESM(require("node:path"));
|
|
49368
49485
|
init_agency_profile();
|
|
@@ -49379,6 +49496,10 @@ var init_report_brand = __esm({
|
|
|
49379
49496
|
TEXT_BACKGROUNDS = ["surface", "bg", "surface-sunk"];
|
|
49380
49497
|
GHL_FOOTER = "Made with GHL Command \xB7 ghlcommand.com";
|
|
49381
49498
|
LOGO_MAX_BYTES = 256 * 1024;
|
|
49499
|
+
LOGO_SOURCE_MAX_BYTES = 20 * 1024 * 1024;
|
|
49500
|
+
preparing = /* @__PURE__ */ new Map();
|
|
49501
|
+
notSmallEnough = /* @__PURE__ */ new Set();
|
|
49502
|
+
fileKey = (abs, st) => `${abs}|${st.size}|${st.mtimeMs}`;
|
|
49382
49503
|
RASTER_TYPES = [
|
|
49383
49504
|
{ mime: "image/png", test: (b) => b.length > 8 && b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) },
|
|
49384
49505
|
{ mime: "image/jpeg", test: (b) => b.length > 3 && b[0] === 255 && b[1] === 216 && b[2] === 255 },
|
|
@@ -50107,7 +50228,7 @@ function planFindCard(nodes, offer, ctx) {
|
|
|
50107
50228
|
if (tailIds.has(str12(n.id) ?? "")) continue;
|
|
50108
50229
|
after.push(n);
|
|
50109
50230
|
}
|
|
50110
|
-
const at = (id2,
|
|
50231
|
+
const at = (id2, path67, want, what, mode) => ({ nodeId: id2, path: path67, want, what, ...mode ? { mode } : {} });
|
|
50111
50232
|
const checks = [
|
|
50112
50233
|
at(findId, ["type"], "find_opportunity", "the new Find opportunity step"),
|
|
50113
50234
|
at(findId, ["attributes", "__customInputFields__", "[filterField=pipeline_id]", "secondValue"], pipelineId, "the pipeline the Find opportunity step looks in"),
|
|
@@ -50206,9 +50327,9 @@ function stored(node) {
|
|
|
50206
50327
|
a = normalizeInternalCreateOpportunityAction(a);
|
|
50207
50328
|
return asObj10(a);
|
|
50208
50329
|
}
|
|
50209
|
-
function pick2(node,
|
|
50330
|
+
function pick2(node, path67) {
|
|
50210
50331
|
let cur = node;
|
|
50211
|
-
for (const seg of
|
|
50332
|
+
for (const seg of path67) {
|
|
50212
50333
|
const sel = /^\[(\w+)=(.*)\]$/.exec(seg);
|
|
50213
50334
|
if (sel) {
|
|
50214
50335
|
cur = asArr6(cur).map(asObj10).find((x) => String(x[sel[1]]) === sel[2]);
|
|
@@ -50751,6 +50872,20 @@ async function accountUsers(client, locationId2) {
|
|
|
50751
50872
|
email: str15(u.email)
|
|
50752
50873
|
}));
|
|
50753
50874
|
}
|
|
50875
|
+
async function fastForwardRefusals(s) {
|
|
50876
|
+
if (s.workflowIds.length > 5) return ["A fast-forward check copies at most 5 workflows."];
|
|
50877
|
+
let owner;
|
|
50878
|
+
if (s.alertUserId) {
|
|
50879
|
+
if (!(await accountUsers(s.client, s.locationId)).some((u) => u.id === s.alertUserId)) return ["The person chosen to receive staff alerts is not a user of this account any more. Pick someone from the list again."];
|
|
50880
|
+
owner = s.alertUserId;
|
|
50881
|
+
} else owner = await ownerUserId(s.client, s.locationId, s.ownerEmail);
|
|
50882
|
+
const out = [];
|
|
50883
|
+
for (const id2 of s.workflowIds) {
|
|
50884
|
+
const r = planCopy(toSource(id2, await s.admin.get(id2)), { ownerUserId: owner, allowedHosts: s.allowedHosts });
|
|
50885
|
+
if (!r.ok) out.push(r.refusal);
|
|
50886
|
+
}
|
|
50887
|
+
return out;
|
|
50888
|
+
}
|
|
50754
50889
|
async function sweepLeftovers(admin, locationId2, base, now) {
|
|
50755
50890
|
const keep = listRuns(locationId2, base).filter((r) => r.status === "watching").flatMap((r) => r.fastForward?.copies.map((c) => c.copyId) ?? []);
|
|
50756
50891
|
return sweep(admin, locationId2, base, now, keep);
|
|
@@ -51804,7 +51939,7 @@ async function readVerifyAccount(saved, client, builderSource = () => workflowRe
|
|
|
51804
51939
|
} };
|
|
51805
51940
|
const plannedTags = new Set(planned.filter((p) => p.type === "tag").map((p) => p.name.trim().toLowerCase()));
|
|
51806
51941
|
const meantLive = meantLiveRefs(saved);
|
|
51807
|
-
const
|
|
51942
|
+
const held2 = heldDraftGates(saved);
|
|
51808
51943
|
await Promise.allSettled(records.filter((r) => r.type === "workflow" && r.id).map(async (record) => {
|
|
51809
51944
|
try {
|
|
51810
51945
|
const full = await builder.getWorkflow(record.id);
|
|
@@ -51839,8 +51974,8 @@ async function readVerifyAccount(saved, client, builderSource = () => workflowRe
|
|
|
51839
51974
|
const draft = full.status !== "published";
|
|
51840
51975
|
if (errors.length || goneTags.length || draft && full.status !== "draft") record.status = "invalid";
|
|
51841
51976
|
else if (unreadable.length) record.status = "unreadable";
|
|
51842
|
-
else if (full.status === "draft") record.status =
|
|
51843
|
-
const heldGates = full.status === "draft" && !errors.length && !goneTags.length && !unreadable.length ?
|
|
51977
|
+
else if (full.status === "draft") record.status = held2.has(record.ref) || meantLive.has(record.ref) ? "invalid" : "pending";
|
|
51978
|
+
const heldGates = full.status === "draft" && !errors.length && !goneTags.length && !unreadable.length ? held2.get(record.ref) : void 0;
|
|
51844
51979
|
if (heldGates) record.held = true;
|
|
51845
51980
|
record.detail = heldGates ? heldDraftDetail(heldGates) : [record.status === "pending" ? "This workflow is built and waiting for you to switch it on in GoHighLevel under Automation > Workflows." : draft && full.status === "draft" && meantLive.has(record.ref) && !errors.length ? SHOULD_BE_LIVE : draft ? `Workflow status is ${full.status}.` : "", ...errors.map((f) => f.message), ...goneTags.map((t) => `It uses the tag "${t}", which is no longer in the account. Run Build again to put it back.`), ...unreadable.map((f) => `${f.message} ${lists.get(f.category)?.error || ""}`)].filter(Boolean).join(" ");
|
|
51846
51981
|
const triggers = Array.isArray(full.triggers) ? full.triggers : [];
|
|
@@ -52589,8 +52724,8 @@ function executablePlanLines(saved) {
|
|
|
52589
52724
|
const refs3 = [...names.keys()].sort((a, b) => b.length - a.length);
|
|
52590
52725
|
const refPattern = refs3.length ? new RegExp(`(?<![\\w./:@])(?:${refs3.map(escapeRE).join("|")})(?![\\w]|\\.[\\w])`, "g") : void 0;
|
|
52591
52726
|
const missingRefPattern = /(?<![\w./:@])(user|pipeline|stage|field|tag|workflow|form|calendar|email_template|sms_template|email|sms|cv|handoff|funnel)\.[a-z0-9_]+(?![\w]|\.[\w])/g;
|
|
52592
|
-
const
|
|
52593
|
-
const renderRefs = (s) =>
|
|
52727
|
+
const resolve15 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
|
|
52728
|
+
const renderRefs = (s) => resolve15(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
|
|
52594
52729
|
const mergeLabel = (field2) => {
|
|
52595
52730
|
const [group, ...parts] = field2.trim().split(".");
|
|
52596
52731
|
const key2 = parts.join(".");
|
|
@@ -54598,9 +54733,9 @@ function distillProof(brief) {
|
|
|
54598
54733
|
if (quote2) proof.testimonials = [{ text: quote2, consent }];
|
|
54599
54734
|
if (Object.keys(proof).length === 0) delete brief.proof;
|
|
54600
54735
|
}
|
|
54601
|
-
function setPath(target,
|
|
54736
|
+
function setPath(target, path67, value) {
|
|
54602
54737
|
if (value === void 0) return;
|
|
54603
|
-
const parts =
|
|
54738
|
+
const parts = path67.split(".");
|
|
54604
54739
|
let node = target;
|
|
54605
54740
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
54606
54741
|
const k = parts[i];
|
|
@@ -54893,10 +55028,10 @@ function livePending(pending, nowMs) {
|
|
|
54893
55028
|
return out;
|
|
54894
55029
|
}
|
|
54895
55030
|
function applyStaffPending(check, pending, nowMs) {
|
|
54896
|
-
const
|
|
54897
|
-
const waiting = check.missing.filter((m) =>
|
|
55031
|
+
const held2 = livePending(pending, nowMs);
|
|
55032
|
+
const waiting = check.missing.filter((m) => held2[normEmail(m.email)]);
|
|
54898
55033
|
if (!waiting.length) return check;
|
|
54899
|
-
const isHeld = (email) => Boolean(
|
|
55034
|
+
const isHeld = (email) => Boolean(held2[normEmail(email)]);
|
|
54900
55035
|
const lines = new Map(waiting.map((m) => [normEmail(m.email), staffWaitingLine(m.name)]));
|
|
54901
55036
|
return {
|
|
54902
55037
|
...check,
|
|
@@ -56554,10 +56689,10 @@ var init_build_obligations = __esm({
|
|
|
56554
56689
|
});
|
|
56555
56690
|
|
|
56556
56691
|
// src/command-os/cockpit-routes.ts
|
|
56557
|
-
function routeOf(method,
|
|
56692
|
+
function routeOf(method, path67) {
|
|
56558
56693
|
const m = method === "HEAD" ? "GET" : method;
|
|
56559
56694
|
const fits = (r) => r.method === "ANY" || r.method === m;
|
|
56560
|
-
return COCKPIT_ROUTES.find((r) => r.match === "exact" && r.path ===
|
|
56695
|
+
return COCKPIT_ROUTES.find((r) => r.match === "exact" && r.path === path67 && fits(r)) ?? COCKPIT_ROUTES.find((r) => r.match === "pattern" && r.pattern.test(path67) && fits(r)) ?? COCKPIT_ROUTES.find((r) => r.match === "prefix" && path67.startsWith(r.path) && path67.length > r.path.length && fits(r));
|
|
56561
56696
|
}
|
|
56562
56697
|
function routeAllows(route, plan) {
|
|
56563
56698
|
return route.plans.includes(plan ?? "");
|
|
@@ -56574,10 +56709,10 @@ var init_cockpit_routes = __esm({
|
|
|
56574
56709
|
OS = ["command-os"];
|
|
56575
56710
|
BOTH = ["pro", "command-os"];
|
|
56576
56711
|
PRO_ONLY = ["pro"];
|
|
56577
|
-
page = (
|
|
56578
|
-
get = (
|
|
56579
|
-
post = (
|
|
56580
|
-
any = (
|
|
56712
|
+
page = (path67, scope, plans, match = "exact", pattern) => ({ method: "GET", path: path67, match, pattern, scope, plans, write: false, kind: "page" });
|
|
56713
|
+
get = (path67, scope, plans) => ({ method: "GET", path: path67, match: "exact", scope, plans, write: false, kind: "api" });
|
|
56714
|
+
post = (path67, scope, plans) => ({ method: "POST", path: path67, match: "exact", scope, plans, write: true, kind: "api" });
|
|
56715
|
+
any = (path67, scope, plans, write, match = "exact", kind = "api") => ({ method: "ANY", path: path67, match, scope, plans, write, kind });
|
|
56581
56716
|
none = (r) => ({ ...r, client: { none: true } });
|
|
56582
56717
|
via = (r, client) => ({ ...r, client });
|
|
56583
56718
|
COCKPIT_ROUTES = [
|
|
@@ -56872,22 +57007,22 @@ function helperPresence(opts) {
|
|
|
56872
57007
|
}
|
|
56873
57008
|
};
|
|
56874
57009
|
}
|
|
56875
|
-
var
|
|
57010
|
+
var import_node_child_process4, import_node_crypto25, fs39, path38, import_zod79, execHelper, HelperOut, PinSchema, SIGN_TIMEOUT_MS, READ_TIMEOUT_MS, DIGEST_HEX;
|
|
56876
57011
|
var init_presence = __esm({
|
|
56877
57012
|
"src/command-os/brain/presence.ts"() {
|
|
56878
57013
|
"use strict";
|
|
56879
|
-
|
|
57014
|
+
import_node_child_process4 = require("node:child_process");
|
|
56880
57015
|
import_node_crypto25 = require("node:crypto");
|
|
56881
57016
|
fs39 = __toESM(require("node:fs"));
|
|
56882
57017
|
path38 = __toESM(require("node:path"));
|
|
56883
57018
|
import_zod79 = require("zod");
|
|
56884
57019
|
init_shapes2();
|
|
56885
|
-
execHelper = (file3, args, timeoutMs) => new Promise((
|
|
56886
|
-
(0,
|
|
57020
|
+
execHelper = (file3, args, timeoutMs) => new Promise((resolve15) => {
|
|
57021
|
+
(0, import_node_child_process4.execFile)(file3, args, { timeout: timeoutMs, maxBuffer: 64 * 1024, shell: false, windowsHide: true, encoding: "utf8" }, (err, stdout) => {
|
|
56887
57022
|
const code = err && "code" in err ? err.code : null;
|
|
56888
|
-
if (code === "ENOENT" || code === "EACCES") return
|
|
56889
|
-
if (err && "killed" in err && err.killed) return
|
|
56890
|
-
|
|
57023
|
+
if (code === "ENOENT" || code === "EACCES") return resolve15({ code: null, stdout: "", missing: true });
|
|
57024
|
+
if (err && "killed" in err && err.killed) return resolve15({ code: null, stdout: "", timedOut: true });
|
|
57025
|
+
resolve15({ code: err ? typeof code === "number" ? code : null : 0, stdout: typeof stdout === "string" ? stdout : "" });
|
|
56891
57026
|
});
|
|
56892
57027
|
});
|
|
56893
57028
|
HelperOut = import_zod79.z.object({
|
|
@@ -57387,10 +57522,10 @@ async function leadLabels(deps, locationId2, contactIds) {
|
|
|
57387
57522
|
await Promise.all(unique.map(async (id2) => {
|
|
57388
57523
|
const stop3 = new AbortController();
|
|
57389
57524
|
let timer;
|
|
57390
|
-
const late = new Promise((
|
|
57525
|
+
const late = new Promise((resolve15) => {
|
|
57391
57526
|
timer = setTimeout(() => {
|
|
57392
57527
|
stop3.abort(new Error("lead lookup timed out"));
|
|
57393
|
-
|
|
57528
|
+
resolve15(null);
|
|
57394
57529
|
}, LEAD_LOOKUP_MS);
|
|
57395
57530
|
});
|
|
57396
57531
|
try {
|
|
@@ -58819,8 +58954,8 @@ async function checkLead(deps, ghl, store2, args) {
|
|
|
58819
58954
|
const check = runPrecheck(args.text, precheckContext(briefNow, client, glossary, rules, optOutWhy({ brainOptedOut: b.brief.optedOut, contact, messages }), now));
|
|
58820
58955
|
const failed = check.checks.filter((c) => !c.pass);
|
|
58821
58956
|
if (failed.length) return stop("checks", `A check fails now: ${failed.map((c) => `${c.check} (${c.why ?? ""})`).join("; ")}`);
|
|
58822
|
-
const
|
|
58823
|
-
if (
|
|
58957
|
+
const held2 = await gateAgain(ghl, store2, { client, d, text: args.text, contactId, now, brief: briefNow, glossary, firstName: contact.firstName ?? null, ...live ? { live } : {} });
|
|
58958
|
+
if (held2) return held2;
|
|
58824
58959
|
return { channel, day: today2.date, messages, sentToday: sentToday2, contact };
|
|
58825
58960
|
}
|
|
58826
58961
|
async function gateAgain(ghl, store2, x) {
|
|
@@ -58895,15 +59030,15 @@ async function checkTextNow(deps, store2, x) {
|
|
|
58895
59030
|
const check = runPrecheck(x.text, precheckContext(b.brief, x.client, glossary, rules, optOutWhy({ brainOptedOut: b.brief.optedOut, contact }), x.now));
|
|
58896
59031
|
const found = findingsOf(check.checks.filter((c) => !c.pass)).map((f) => `${checkLabel(f.check)}: ${f.found}.`);
|
|
58897
59032
|
if (found.length) return { ok: true, found };
|
|
58898
|
-
const
|
|
58899
|
-
if (
|
|
59033
|
+
const held2 = await gateAgain(ghl, store2, { client: x.client, d: x.d, text: x.text, contactId, now: x.now, brief: b.brief, glossary, firstName: contact.firstName ?? null });
|
|
59034
|
+
if (held2 && !held2.ok && held2.sent === false) return { ok: true, found: [held2.why.replace(/^Not sent: /, "")] };
|
|
58900
59035
|
return { ok: true, found: [] };
|
|
58901
59036
|
}
|
|
58902
59037
|
function composeBody(channel, contactId, text3, businessName, emailTo) {
|
|
58903
59038
|
return channel === "SMS" ? { type: "SMS", contactId, message: text3 } : { type: "Email", contactId, subject: `Re: your message to ${businessName}`, html: `<p style="margin:0px;">${escHtml2(text3)}</p>`, ...emailTo ? { emailTo } : {} };
|
|
58904
59039
|
}
|
|
58905
|
-
function productionPath(deps,
|
|
58906
|
-
return
|
|
59040
|
+
function productionPath(deps, path67) {
|
|
59041
|
+
return path67 === "cockpit" && deps.liveSendEnabled?.() === true && deps.outOfBandReady?.() === true;
|
|
58907
59042
|
}
|
|
58908
59043
|
function sentNote(x) {
|
|
58909
59044
|
const who2 = x.path === "sandbox" ? "from the sandbox send command" : `approved by ${x.approvedBy ?? "the owner"} in the Command OS cockpit`;
|
|
@@ -59932,7 +60067,7 @@ function json2(res, status, body) {
|
|
|
59932
60067
|
res.end(JSON.stringify(body));
|
|
59933
60068
|
}
|
|
59934
60069
|
function readJson(req, limit) {
|
|
59935
|
-
return new Promise((
|
|
60070
|
+
return new Promise((resolve15, reject) => {
|
|
59936
60071
|
let size2 = 0;
|
|
59937
60072
|
const chunks = [];
|
|
59938
60073
|
req.on("data", (c) => {
|
|
@@ -59944,7 +60079,7 @@ function readJson(req, limit) {
|
|
|
59944
60079
|
});
|
|
59945
60080
|
req.on("end", () => {
|
|
59946
60081
|
try {
|
|
59947
|
-
|
|
60082
|
+
resolve15(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
59948
60083
|
} catch {
|
|
59949
60084
|
reject(new Error("not json"));
|
|
59950
60085
|
}
|
|
@@ -59955,24 +60090,24 @@ function readJson(req, limit) {
|
|
|
59955
60090
|
function createBrainRoutes(opts) {
|
|
59956
60091
|
return (req, res) => {
|
|
59957
60092
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
59958
|
-
const
|
|
59959
|
-
if (
|
|
60093
|
+
const path67 = u.pathname;
|
|
60094
|
+
if (path67 !== "/brain" && path67 !== "/brain/handoff" && path67 !== MASTER_PAGE_PATH && !path67.startsWith("/api/brain/")) return false;
|
|
59960
60095
|
if (opts.plan !== "command-os") {
|
|
59961
60096
|
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
|
|
59962
60097
|
res.end(opts.upgradeMessage.trim());
|
|
59963
60098
|
return true;
|
|
59964
60099
|
}
|
|
59965
60100
|
const onBoard = (id2) => opts.accounts().find((a) => a.locationId === id2);
|
|
59966
|
-
if (req.method === "GET" &&
|
|
60101
|
+
if (req.method === "GET" && path67 === "/brain/handoff") {
|
|
59967
60102
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
59968
60103
|
res.end(brainHandoffPageHtml(opts.seatFor(req), opts.seatHeader));
|
|
59969
60104
|
return true;
|
|
59970
60105
|
}
|
|
59971
|
-
if (req.method === "GET" &&
|
|
60106
|
+
if (req.method === "GET" && path67 === "/api/brain/handoff") {
|
|
59972
60107
|
void handoffView(opts.handoffDeps()).then((v) => json2(res, 200, v)).catch(() => json2(res, 200, { ok: false, error: "Your brain could not be read. Check your internet connection and the Brain card in Connections, then Refresh." }));
|
|
59973
60108
|
return true;
|
|
59974
60109
|
}
|
|
59975
|
-
if (req.method === "POST" &&
|
|
60110
|
+
if (req.method === "POST" && path67 === "/api/brain/export") {
|
|
59976
60111
|
void exportBundle(opts.handoffDeps()).then((r) => {
|
|
59977
60112
|
if (!r.ok) {
|
|
59978
60113
|
json2(res, 200, r);
|
|
@@ -59985,7 +60120,7 @@ function createBrainRoutes(opts) {
|
|
|
59985
60120
|
});
|
|
59986
60121
|
return true;
|
|
59987
60122
|
}
|
|
59988
|
-
if (req.method === "POST" &&
|
|
60123
|
+
if (req.method === "POST" && path67 === "/api/brain/pause") {
|
|
59989
60124
|
void readJson(req, 2e3).then(async (body) => {
|
|
59990
60125
|
const parsed2 = PauseRequest.safeParse(body);
|
|
59991
60126
|
if (!parsed2.success) {
|
|
@@ -59998,13 +60133,13 @@ function createBrainRoutes(opts) {
|
|
|
59998
60133
|
});
|
|
59999
60134
|
return true;
|
|
60000
60135
|
}
|
|
60001
|
-
if (
|
|
60136
|
+
if (path67 === MASTER_PAGE_PATH || path67 === MASTER_API_PATH) {
|
|
60002
60137
|
if (req.method !== "GET") {
|
|
60003
60138
|
res.writeHead(405, { "Content-Type": "application/json", "Cache-Control": "no-store", Allow: "GET" });
|
|
60004
60139
|
res.end(JSON.stringify({ ok: false, error: "This page is read only." }));
|
|
60005
60140
|
return true;
|
|
60006
60141
|
}
|
|
60007
|
-
if (
|
|
60142
|
+
if (path67 === MASTER_PAGE_PATH) {
|
|
60008
60143
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
60009
60144
|
res.end(brainMasterPageHtml(opts.seatFor(req), opts.seatHeader));
|
|
60010
60145
|
return true;
|
|
@@ -60017,14 +60152,14 @@ function createBrainRoutes(opts) {
|
|
|
60017
60152
|
void Promise.resolve().then(() => masterView(md())).then((v) => json2(res, 200, v)).catch(() => json2(res, 200, { ok: false, error: "The brain could not be read. Check the Brain card in Connections, then Refresh." }));
|
|
60018
60153
|
return true;
|
|
60019
60154
|
}
|
|
60020
|
-
if (req.method === "GET" &&
|
|
60155
|
+
if (req.method === "GET" && path67 === "/brain") {
|
|
60021
60156
|
const accounts = [...opts.accounts()].sort((a, b) => a.name.localeCompare(b.name));
|
|
60022
|
-
const client = onBoard(u.searchParams.get("client") ?? "");
|
|
60157
|
+
const client = onBoard(u.searchParams.get("client") ?? "") ?? (accounts.length === 1 && !u.searchParams.get("client") ? onBoard(accounts[0].locationId) : void 0);
|
|
60023
60158
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
60024
60159
|
res.end(brainApprovePageHtml({ accounts, client }, opts.seatFor(req), opts.seatHeader));
|
|
60025
60160
|
return true;
|
|
60026
60161
|
}
|
|
60027
|
-
if (req.method === "GET" &&
|
|
60162
|
+
if (req.method === "GET" && path67 === BRAIN_PENDING_PATH) {
|
|
60028
60163
|
const client = onBoard(u.searchParams.get("client") ?? "");
|
|
60029
60164
|
if (!client) {
|
|
60030
60165
|
json2(res, 200, { ok: false, error: "That account is not on the board." });
|
|
@@ -60033,7 +60168,7 @@ function createBrainRoutes(opts) {
|
|
|
60033
60168
|
void listPending(opts.deps(), client.locationId).then((view2) => json2(res, 200, view2)).catch(() => json2(res, 200, { ok: false, error: "The brain could not be read. Check the Brain card in Connections, then Refresh." }));
|
|
60034
60169
|
return true;
|
|
60035
60170
|
}
|
|
60036
|
-
if (req.method === "GET" &&
|
|
60171
|
+
if (req.method === "GET" && path67 === BRAIN_SEND_LOG_PATH) {
|
|
60037
60172
|
const client = onBoard(u.searchParams.get("client") ?? "");
|
|
60038
60173
|
if (!client) {
|
|
60039
60174
|
json2(res, 200, { ok: false, error: "That account is not on the board." });
|
|
@@ -60043,7 +60178,7 @@ function createBrainRoutes(opts) {
|
|
|
60043
60178
|
json2(res, 200, rows2 === "unconfirmed" ? { ok: false, error: "The send log on this computer (brain-send-log.jsonl in the Command OS app-data folder) could not be read. Nothing can be sent until it is repaired or moved." } : { ok: true, client: client.name, rows: rows2 });
|
|
60044
60179
|
return true;
|
|
60045
60180
|
}
|
|
60046
|
-
if (req.method === "POST" && (
|
|
60181
|
+
if (req.method === "POST" && (path67 === "/api/brain/decide" || path67 === "/api/brain/send" || path67 === "/api/brain/send-mode" || path67 === "/api/brain/confirm-mappings")) {
|
|
60047
60182
|
void readJson(req, 2e4).then(async (body) => {
|
|
60048
60183
|
try {
|
|
60049
60184
|
await answer2(body);
|
|
@@ -60054,7 +60189,7 @@ function createBrainRoutes(opts) {
|
|
|
60054
60189
|
if (!res.headersSent) json2(res, 400, { ok: false, wrote: "nothing", error: "Send a valid request." });
|
|
60055
60190
|
});
|
|
60056
60191
|
const answer2 = async (body) => {
|
|
60057
|
-
if (
|
|
60192
|
+
if (path67 === "/api/brain/decide") {
|
|
60058
60193
|
const parsed3 = DecideRequest.safeParse(body);
|
|
60059
60194
|
if (!parsed3.success) {
|
|
60060
60195
|
json2(res, 400, { ok: false, wrote: "nothing", error: "That request is not a decision this screen makes." });
|
|
@@ -60067,7 +60202,7 @@ function createBrainRoutes(opts) {
|
|
|
60067
60202
|
json2(res, 200, parsed3.data.preview ? await previewDecision(opts.deps(), parsed3.data) : await decide(opts.deps(), parsed3.data));
|
|
60068
60203
|
return;
|
|
60069
60204
|
}
|
|
60070
|
-
if (
|
|
60205
|
+
if (path67 === "/api/brain/confirm-mappings") {
|
|
60071
60206
|
const parsed3 = ConfirmMappingsRequest.safeParse(body);
|
|
60072
60207
|
if (!parsed3.success) {
|
|
60073
60208
|
json2(res, 400, { ok: false, wrote: "nothing", error: "That request is not a confirmation this screen makes." });
|
|
@@ -60080,7 +60215,7 @@ function createBrainRoutes(opts) {
|
|
|
60080
60215
|
json2(res, 200, await confirmMappings(opts.deps(), parsed3.data));
|
|
60081
60216
|
return;
|
|
60082
60217
|
}
|
|
60083
|
-
if (
|
|
60218
|
+
if (path67 === "/api/brain/send-mode") {
|
|
60084
60219
|
const parsed3 = SendModeRequest.safeParse(body);
|
|
60085
60220
|
if (!parsed3.success) {
|
|
60086
60221
|
json2(res, 400, { ok: false, wrote: "nothing", error: "That request is not a send-mode change this screen makes." });
|
|
@@ -60821,11 +60956,11 @@ function defaultHasSavedSetup() {
|
|
|
60821
60956
|
return false;
|
|
60822
60957
|
}
|
|
60823
60958
|
}
|
|
60824
|
-
var
|
|
60959
|
+
var import_node_child_process5, import_node_fs14, GHL_SERVER_NAME, CLAUDE_CODE_ADD_ARGS, CLAUDE_CODE_INSTALL_LINE, CLAUDE_CODE_ADD_HELP, OURS, defaultRunner;
|
|
60825
60960
|
var init_claude_code_registration = __esm({
|
|
60826
60961
|
"src/claude-code-registration.ts"() {
|
|
60827
60962
|
"use strict";
|
|
60828
|
-
|
|
60963
|
+
import_node_child_process5 = require("node:child_process");
|
|
60829
60964
|
init_claude_locate();
|
|
60830
60965
|
init_running_channel();
|
|
60831
60966
|
import_node_fs14 = require("node:fs");
|
|
@@ -60836,7 +60971,7 @@ var init_claude_code_registration = __esm({
|
|
|
60836
60971
|
CLAUDE_CODE_INSTALL_LINE = "npx -y @elitedcs/ghl-mcp@latest cli install --claude-code";
|
|
60837
60972
|
CLAUDE_CODE_ADD_HELP = claudeCodeAddHelp("latest");
|
|
60838
60973
|
OURS = /ghl-mcp|start-mcp\.sh|dist[\\/]index\.js/i;
|
|
60839
|
-
defaultRunner = (command, args, env2, timeoutMs) => new Promise((
|
|
60974
|
+
defaultRunner = (command, args, env2, timeoutMs) => new Promise((resolve15) => {
|
|
60840
60975
|
let stdout = "";
|
|
60841
60976
|
let stderr = "";
|
|
60842
60977
|
let settled = false;
|
|
@@ -60845,12 +60980,12 @@ var init_claude_code_registration = __esm({
|
|
|
60845
60980
|
if (!settled) {
|
|
60846
60981
|
settled = true;
|
|
60847
60982
|
if (timer) clearTimeout(timer);
|
|
60848
|
-
|
|
60983
|
+
resolve15(r);
|
|
60849
60984
|
}
|
|
60850
60985
|
};
|
|
60851
60986
|
let child;
|
|
60852
60987
|
try {
|
|
60853
|
-
child = (0,
|
|
60988
|
+
child = (0, import_node_child_process5.spawn)(command, args, { stdio: ["ignore", "pipe", "pipe"], env: env2 });
|
|
60854
60989
|
} catch {
|
|
60855
60990
|
done({ code: null, stdout, stderr, failed: true });
|
|
60856
60991
|
return;
|
|
@@ -61099,7 +61234,7 @@ function startDesignRun(request2, opts = {}) {
|
|
|
61099
61234
|
const job = { id: id2, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), outDir, request: request2, tail: [] };
|
|
61100
61235
|
jobs.set(id2, job);
|
|
61101
61236
|
const entry = opts.entry ?? process.argv[1];
|
|
61102
|
-
const spawner = opts.spawnFn ??
|
|
61237
|
+
const spawner = opts.spawnFn ?? import_node_child_process6.spawn;
|
|
61103
61238
|
const child = spawner(process.execPath, [entry, ...argvFor(request2, outDir)], {
|
|
61104
61239
|
cwd: outDir,
|
|
61105
61240
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -61160,11 +61295,11 @@ function cancelDesignRun(id2) {
|
|
|
61160
61295
|
}
|
|
61161
61296
|
return true;
|
|
61162
61297
|
}
|
|
61163
|
-
var
|
|
61298
|
+
var import_node_child_process6, import_node_fs15, import_node_path13, import_node_crypto29, SLUG2, MODEL, JOINED_FLAG, jobs, children;
|
|
61164
61299
|
var init_run_jobs = __esm({
|
|
61165
61300
|
"src/command-os/design/run-jobs.ts"() {
|
|
61166
61301
|
"use strict";
|
|
61167
|
-
|
|
61302
|
+
import_node_child_process6 = require("node:child_process");
|
|
61168
61303
|
import_node_fs15 = require("node:fs");
|
|
61169
61304
|
import_node_path13 = require("node:path");
|
|
61170
61305
|
import_node_crypto29 = require("node:crypto");
|
|
@@ -61252,14 +61387,14 @@ var init_run_cost = __esm({
|
|
|
61252
61387
|
// src/claude-call.ts
|
|
61253
61388
|
function callClaudeRecorded(bin, argv, context2, onCost = () => {
|
|
61254
61389
|
}) {
|
|
61255
|
-
return new Promise((
|
|
61390
|
+
return new Promise((resolve15, reject) => {
|
|
61256
61391
|
const run = new RunTexts();
|
|
61257
61392
|
let buffer = "";
|
|
61258
61393
|
let stderr = "";
|
|
61259
61394
|
let facts = {};
|
|
61260
61395
|
let settled = false;
|
|
61261
61396
|
const cmd = claudeCommand(bin, [...argv, "--output-format", "stream-json", "--verbose", ...NO_MEMBER_HOOKS]);
|
|
61262
|
-
const child = (0,
|
|
61397
|
+
const child = (0, import_node_child_process7.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
61263
61398
|
const timer = setTimeout(() => child.kill("SIGKILL"), 6 * 60 * 1e3);
|
|
61264
61399
|
const finish3 = (ok) => {
|
|
61265
61400
|
if (settled) return;
|
|
@@ -61271,7 +61406,7 @@ function callClaudeRecorded(bin, argv, context2, onCost = () => {
|
|
|
61271
61406
|
onCost("The cost record could not be saved.");
|
|
61272
61407
|
}
|
|
61273
61408
|
onCost(`${context2.kind}: ${facts.costUsd === void 0 ? "cost not reported" : formatUsd(facts.costUsd)}.`);
|
|
61274
|
-
if (ok)
|
|
61409
|
+
if (ok) resolve15(run.pick());
|
|
61275
61410
|
else reject(new Error("Check your Claude authentication in Your agency before reading this source."));
|
|
61276
61411
|
};
|
|
61277
61412
|
const consume = (line4) => {
|
|
@@ -61300,11 +61435,11 @@ function callClaudeRecorded(bin, argv, context2, onCost = () => {
|
|
|
61300
61435
|
});
|
|
61301
61436
|
});
|
|
61302
61437
|
}
|
|
61303
|
-
var
|
|
61438
|
+
var import_node_child_process7;
|
|
61304
61439
|
var init_claude_call = __esm({
|
|
61305
61440
|
"src/claude-call.ts"() {
|
|
61306
61441
|
"use strict";
|
|
61307
|
-
|
|
61442
|
+
import_node_child_process7 = require("node:child_process");
|
|
61308
61443
|
init_run_cost();
|
|
61309
61444
|
init_claude_cli();
|
|
61310
61445
|
init_claude_locate();
|
|
@@ -61434,9 +61569,10 @@ function calendarStaff(h, facts, planned) {
|
|
|
61434
61569
|
}
|
|
61435
61570
|
return { done: true, teams };
|
|
61436
61571
|
}
|
|
61437
|
-
function withBuiltCalendarTeams(facts, saved) {
|
|
61438
|
-
const built =
|
|
61439
|
-
|
|
61572
|
+
function withBuiltCalendarTeams(facts, saved, journal2 = {}) {
|
|
61573
|
+
const built = { ...journal2 };
|
|
61574
|
+
for (const [name2, ids] of Object.entries(saved?.calendarTeamsBuilt ?? {})) built[name2] = [.../* @__PURE__ */ new Set([...built[name2] ?? [], ...ids])];
|
|
61575
|
+
return Object.keys(built).length ? { ...facts, calendarTeamsBuilt: built } : facts;
|
|
61440
61576
|
}
|
|
61441
61577
|
function mergeCalendarTeamsBuilt(kept, built) {
|
|
61442
61578
|
return { ...kept ?? {}, ...Object.fromEntries(built.filter((b) => b.type === "calendar" && b.teamIds?.length).map((b) => [b.name.trim().toLowerCase(), [...b.teamIds]])) };
|
|
@@ -61571,6 +61707,22 @@ function phoneInfo(facts) {
|
|
|
61571
61707
|
if (phones.some((p) => digits(p) === digits(biz))) return void 0;
|
|
61572
61708
|
return `Your texts come from ${phones[0]}; your business line is ${biz}. That is fine if the business line is the clinic's own number.`;
|
|
61573
61709
|
}
|
|
61710
|
+
function fitHandoff(h, all, facts) {
|
|
61711
|
+
let out = h;
|
|
61712
|
+
const booking = facts?.customValues?.find((v) => v.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_") === "booking_link")?.value;
|
|
61713
|
+
if (typeof booking === "string" && booking.trim() && out.instruction && /booking_link custom value/i.test(out.instruction)) {
|
|
61714
|
+
out = { ...out, instruction: out.instruction.replace(/\s*[^.]*\bbooking_link custom value\.?/i, "").trim() };
|
|
61715
|
+
}
|
|
61716
|
+
if (out.ref === "handoff.business_details" && all.some((x) => x?.ref === "handoff.phone_number") && /business_phone/.test(out.instruction ?? "")) {
|
|
61717
|
+
out = {
|
|
61718
|
+
...out,
|
|
61719
|
+
title: "Confirm the name messages are signed with",
|
|
61720
|
+
instruction: "Settings > Custom Values: set owner_first_name to the first name every email is signed with. The build pre-fills it from the intake when it can. The business phone is the phone number job above.",
|
|
61721
|
+
successCheck: "owner_first_name shows a real value, not a blank"
|
|
61722
|
+
};
|
|
61723
|
+
}
|
|
61724
|
+
return out;
|
|
61725
|
+
}
|
|
61574
61726
|
function catalogJobs(input) {
|
|
61575
61727
|
const out = [];
|
|
61576
61728
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -61583,11 +61735,12 @@ function catalogJobs(input) {
|
|
|
61583
61735
|
for (const h of input.handoffs) {
|
|
61584
61736
|
if (!h || typeof h.ref !== "string") continue;
|
|
61585
61737
|
const plannedCalendars = input.plannedCalendars ?? /* @__PURE__ */ new Map();
|
|
61586
|
-
const
|
|
61738
|
+
const fit = fitHandoff(h, input.handoffs, input.facts);
|
|
61739
|
+
const state2 = handoffInAccount(fit, input.facts ?? {}, input.readback ?? [], input.plannedUsers ?? [], openRecipientJobs, plannedCalendars);
|
|
61587
61740
|
const text3 = handoffJobText(h);
|
|
61588
61741
|
if (!(state2.readable && state2.done)) {
|
|
61589
|
-
const why = (state2.readable || h.ref === ASSIGN_CALENDAR_STAFF) && input.facts ? handoffReadReason(
|
|
61590
|
-
push({ id: h.ref, text: handoffShownText(
|
|
61742
|
+
const why = (state2.readable || h.ref === ASSIGN_CALENDAR_STAFF) && input.facts ? handoffReadReason(fit, input.facts, plannedCalendars) : void 0;
|
|
61743
|
+
push({ id: h.ref, text: handoffShownText(fit, input.facts), timing: jobTiming(text3), selfReport: !state2.readable, key: requirementsKey([text3]), ...why ? { why } : {} });
|
|
61591
61744
|
}
|
|
61592
61745
|
const part = HANDOFF_PARTS[h.ref];
|
|
61593
61746
|
if (part) {
|
|
@@ -64272,7 +64425,7 @@ function unitLooksLike(unit) {
|
|
|
64272
64425
|
function gateExtraction(transcript, proposed, questions) {
|
|
64273
64426
|
const knownIds = questions;
|
|
64274
64427
|
const accepted = [];
|
|
64275
|
-
const
|
|
64428
|
+
const held2 = [];
|
|
64276
64429
|
const rejected = [];
|
|
64277
64430
|
const patch = {};
|
|
64278
64431
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -64309,10 +64462,10 @@ function gateExtraction(transcript, proposed, questions) {
|
|
|
64309
64462
|
accepted.push(item);
|
|
64310
64463
|
patch[item.questionId] = item.value;
|
|
64311
64464
|
} else {
|
|
64312
|
-
|
|
64465
|
+
held2.push({ ...item, because: cite.because });
|
|
64313
64466
|
}
|
|
64314
64467
|
}
|
|
64315
|
-
return { accepted, held, rejected, patch };
|
|
64468
|
+
return { accepted, held: held2, rejected, patch };
|
|
64316
64469
|
}
|
|
64317
64470
|
function mergeAccepted(ans, result) {
|
|
64318
64471
|
return { ...ans, ...result.patch };
|
|
@@ -64641,10 +64794,10 @@ async function prefillFromSource(spawnClaude, labels, source, toolContext = { se
|
|
|
64641
64794
|
const byLabel = new Map(questions.map((q3) => [q3.label, q3]));
|
|
64642
64795
|
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, toolContext)), labels, source.kind === "text" ? source.text : void 0, byLabel);
|
|
64643
64796
|
result.answers = validatePrefillAnswers(questions, result.answers);
|
|
64644
|
-
for (const [label2,
|
|
64645
|
-
const checked = validatePrefillAnswers(questions, { [label2]:
|
|
64797
|
+
for (const [label2, held2] of Object.entries(result.held)) {
|
|
64798
|
+
const checked = validatePrefillAnswers(questions, { [label2]: held2.value });
|
|
64646
64799
|
if (!(label2 in checked)) delete result.held[label2];
|
|
64647
|
-
else
|
|
64800
|
+
else held2.value = checked[label2];
|
|
64648
64801
|
}
|
|
64649
64802
|
result.unverified = result.unverified.filter((label2) => label2 in result.answers);
|
|
64650
64803
|
result.unknown = labels.filter((label2) => !(label2 in result.answers) && !(label2 in result.held));
|
|
@@ -64738,7 +64891,7 @@ async function runPrefill(call2, labels, material, questions = /* @__PURE__ */ n
|
|
|
64738
64891
|
if (json3 === void 0) return fail("the model did not return a result line");
|
|
64739
64892
|
const parsed2 = JSON.parse(json3);
|
|
64740
64893
|
const answers = {};
|
|
64741
|
-
const
|
|
64894
|
+
const held2 = {};
|
|
64742
64895
|
const unverified = [];
|
|
64743
64896
|
for (const [k, raw] of Object.entries(parsed2.answers ?? {})) {
|
|
64744
64897
|
if (!labels.includes(k)) continue;
|
|
@@ -64756,10 +64909,10 @@ async function runPrefill(call2, labels, material, questions = /* @__PURE__ */ n
|
|
|
64756
64909
|
const checks = (Array.isArray(value) ? value : [value]).map((v) => intakeCitation(material, v, quote2, basis, q3 ? { label: k, options: q3.options } : void 0));
|
|
64757
64910
|
const cite = checks.find((c) => c.verdict === "refused") ?? checks.find((c) => c.verdict === "held") ?? checks[0];
|
|
64758
64911
|
if (cite.verdict === "ok") answers[k] = value;
|
|
64759
|
-
else if (cite.verdict === "held")
|
|
64912
|
+
else if (cite.verdict === "held") held2[k] = { value, because: cite.because, ...cite.claims ? { claims: true } : {} };
|
|
64760
64913
|
}
|
|
64761
|
-
const unknown = labels.filter((l) => !(l in answers) && !(l in
|
|
64762
|
-
return { ok: true, answers, unknown, held, unverified };
|
|
64914
|
+
const unknown = labels.filter((l) => !(l in answers) && !(l in held2));
|
|
64915
|
+
return { ok: true, answers, unknown, held: held2, unverified };
|
|
64763
64916
|
} catch (e) {
|
|
64764
64917
|
return fail(e instanceof Error ? e.message : String(e));
|
|
64765
64918
|
}
|
|
@@ -65225,7 +65378,7 @@ function bsSummary(n,c,state){const id=c.locationId;const acc=((state.accounts||
|
|
|
65225
65378
|
if(n===6){const sc=(state.staffChecks||{})[id];return sc&&sc.matched&&sc.matched.length?"In the account: "+sc.matched.map(m=>m.name).join(", ")+".":"Staff confirmed."}
|
|
65226
65379
|
if(n===7){const a=(state.buildApproved||{})[id];return a?"Approved "+niceDate(a,true)+".":"Approved."}
|
|
65227
65380
|
/* Run 9: the folded step said only "Built.". It says what was made, from the build's own record. */
|
|
65228
|
-
if(n===8){const
|
|
65381
|
+
if(n===8){const bv0=(((state.buildViews||{})[id])||{});const made=(bv0.made&&bv0.made.length)?bv0.made:(bv0.built||[]).filter(x=>x.status==="created");if(!made.length)return "Built.";
|
|
65229
65382
|
const by={};for(const x of made)by[x.type]=(by[x.type]||0)+1;const KIND={pipeline:"pipeline",stage:"pipeline stage",field:"custom field",tag:"tag",cv:"custom value",calendar:"calendar",form:"form",email_template:"email template",sms_template:"text template",workflow:"draft automation",user:"staff login"};
|
|
65230
65383
|
return "Built in GoHighLevel: "+Object.entries(by).map(([k,v])=>v+" "+plural(v,KIND[k]||k)).join(", ")+". Nothing is switched on."}
|
|
65231
65384
|
if(n===9){const oc=(state.outcomes||{})[id+":4"];const must=bsMustDo(c,state).length;
|
|
@@ -65285,7 +65438,7 @@ function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
|
65285
65438
|
if(bp&&!bp.confirmed&&!live){bsPause(c,state,bp.preview,box);return}
|
|
65286
65439
|
bsOutcome(c,state,3,box);
|
|
65287
65440
|
/* Run 8 finding 4: it ended on just "Built." The finish says what was made, counted from the build's own record. */
|
|
65288
|
-
if(!live&&(((state.clients||{})[id]||{}).stages||[])[3]==="done"){const
|
|
65441
|
+
if(!live&&(((state.clients||{})[id]||{}).stages||[])[3]==="done"){const bv0=(((state.buildViews||{})[id])||{});const made=(bv0.made&&bv0.made.length)?bv0.made:(bv0.built||[]).filter(x=>x.status==="created");
|
|
65289
65442
|
if(made.length){const by={};for(const x of made)by[x.type]=(by[x.type]||0)+1;const KIND={pipeline:"pipeline",stage:"pipeline stage",field:"custom field",tag:"tag",cv:"custom value",calendar:"calendar",form:"form",email_template:"email template",sms_template:"text template",workflow:"draft automation",user:"staff login"};
|
|
65290
65443
|
box.appendChild(mk("p","","Made in GoHighLevel this build: "+Object.entries(by).map(([k,n])=>n+" "+plural(n,KIND[k]||k)).join(", ")+". Every automation is a draft until you switch it on."))}}if(!live)bsSnippets(c,state,box);bsRun(c,state,run,3,(((state.clients[id]||{}).stages||[])[3]==="done")?"Build again":"Start the build",box);return}
|
|
65291
65444
|
if(n===9){bsOutcome(c,state,4,box);bsRun(c,state,run,4,(state.outcomes||{})[id+":4"]?"Run the check again":"Run the check",box);if((state.outcomes||{})[id+":4"])bsRebuild(c,state,run,box);return}
|
|
@@ -65620,7 +65773,14 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
|
|
|
65620
65773
|
if(view.approvedAt)d.appendChild(mk("p","","Building approved on "+niceDate(view.approvedAt,true)+"."));page.appendChild(d)}}
|
|
65621
65774
|
/* The step that just opened comes into view, once per change (and on first open), after the redraw keeps its scroll. */
|
|
65622
65775
|
if(bsSeen[id]!==pr.current){bsSeen[id]=pr.current;if(pr.current>0)setTimeout(()=>{const el=document.getElementById("step-"+(pr.current+1));if(el)try{el.scrollIntoView({block:"start",behavior:"smooth"})}catch(e){el.scrollIntoView()}},0)}
|
|
65623
|
-
if(pr.current<0)page.appendChild(mk("p","cds-callout info","All "+n+" steps are done. The build is signed off. Switching the client on is yours to do in GoHighLevel: see step 12's list."));
|
|
65776
|
+
if(pr.current<0){page.appendChild(mk("p","cds-callout info","All "+n+" steps are done. The build is signed off. Switching the client on is yours to do in GoHighLevel: see step 12's list."));
|
|
65777
|
+
/* Walk W26 (2026-10-29): after sign-off nothing could start the build again, and the page still said Built after its
|
|
65778
|
+
objects were deleted in GoHighLevel. The check can always run again; when it finds built objects gone, it says how
|
|
65779
|
+
many and Build again is offered beside it. */
|
|
65780
|
+
const after=mk("div","bs-after-signoff");const gone=((state.builtMissing||{})[id])||0;
|
|
65781
|
+
if(gone)after.appendChild(mk("p","cds-callout warn bs-gone","The last check found "+gone+" "+plural(gone,"thing")+" this build made missing from "+c.name+" in GoHighLevel. Build again to put "+(gone===1?"it":"them")+" back, then run the check."));
|
|
65782
|
+
else after.appendChild(mk("p","muted","Changed something in GoHighLevel since? Check the account again to see it as it is now."));
|
|
65783
|
+
bsRun(c,state,run,4,"Check the account again",after,!gone);if(gone)bsRebuild(c,state,run,after);page.appendChild(after)}
|
|
65624
65784
|
return page}
|
|
65625
65785
|
`;
|
|
65626
65786
|
BS_STAFF_WHY = "Everyone on your team needs their own login in this client's GoHighLevel account. The build only gives appointments, alerts and tasks to people who have one, so each person below must be in the account before the build runs.";
|
|
@@ -65740,7 +65900,7 @@ ${cockpitHeaderHtml("agency")}
|
|
|
65740
65900
|
<h1 class="cds-page-title">Your agency's own details, set once and used on every client</h1>
|
|
65741
65901
|
<p class="cds-page-sub">Filled in once. Reused on every client document, every proposal, and every demo page, so nothing has to ask you again.</p>
|
|
65742
65902
|
</div>
|
|
65743
|
-
<div class="ap-save"><span id="msgTop" role="status"></span><button type="submit" form="f" class="cds-btn">Save your agency details</button></div>
|
|
65903
|
+
<div class="ap-save"><span id="msgTop" role="status"></span><button type="submit" form="f" class="cds-btn" data-msg="msgTop">Save your agency details</button></div>
|
|
65744
65904
|
</div>
|
|
65745
65905
|
${p.agencyName ? "" : ` <div class="cds-callout warn"><span class="ttl">Add an agency name to use this on client documents.</span>Everything else can wait. The name is what every report and proposal carries.</div>
|
|
65746
65906
|
`}
|
|
@@ -65755,7 +65915,7 @@ ${p.agencyName ? "" : ` <div class="cds-callout warn"><span class="ttl">Add an a
|
|
|
65755
65915
|
<div class="cds-field"><label for="accent">Brand color</label><input type="color" class="cds-input" id="accent" name="accent" value="${esc14(p.accent || "#b8934a")}"></div>
|
|
65756
65916
|
</div>
|
|
65757
65917
|
${input("logoUrl", "Logo URL", p.logoUrl, { placeholder: "https://youragency.com/logo.svg", hint: "Must start with https. A logo link that isn't https is refused, because this one goes on pages your prospects open." })}
|
|
65758
|
-
${input("logoFile", "Logo file on this computer (for client reports)", p.logoFile, { placeholder: "/Users/you/Documents/logo.png", hint: "Optional. The full path to a PNG, JPEG, WebP, GIF or SVG
|
|
65918
|
+
${input("logoFile", "Logo file on this computer (for client reports)", p.logoFile, { placeholder: "/Users/you/Documents/logo.png", hint: "Optional. The full path to a PNG, JPEG, WebP, GIF or SVG. Any size: we make a small copy for reports, and your file is not changed. Reports you hand clients carry it inside the page, so it prints and travels with a saved copy." })}
|
|
65759
65919
|
${input("bookingUrl", "Booking link", p.bookingUrl, { placeholder: "https://your-calendar-link", hint: "Your own calendar link, usually a GoHighLevel booking page. The Book the walkthrough button on a report you send a prospect opens it. Must start with https." })}
|
|
65760
65920
|
</section>
|
|
65761
65921
|
<section class="cds-panel">
|
|
@@ -65855,6 +66015,9 @@ ${SERVICE_IDS.map((id2) => ` <label class="cds-check"><input type="checkbox"
|
|
|
65855
66015
|
</ul>
|
|
65856
66016
|
</section>
|
|
65857
66017
|
|
|
66018
|
+
<!-- Jerry's rule (walk 2026-10-29): a long form's action sits at the bottom as well as the top; one submit, one result
|
|
66019
|
+
line, beside the Save that was pressed. -->
|
|
66020
|
+
<div class="ap-save ap-save-bottom"><button type="submit" class="cds-btn" data-msg="msgBottom">Save your agency details</button><span id="msgBottom" role="status"></span></div>
|
|
65858
66021
|
</form>
|
|
65859
66022
|
|
|
65860
66023
|
<div class="cds-grid two ap-start">
|
|
@@ -66003,6 +66166,9 @@ function settlePackages(profile,read){
|
|
|
66003
66166
|
const v=id=>document.getElementById(id).value.trim();
|
|
66004
66167
|
const lines=id=>v(id).split("\\n").map(s=>s.trim()).filter(Boolean);
|
|
66005
66168
|
document.getElementById("f").addEventListener("submit",async e=>{
|
|
66169
|
+
/* The one result line is beside the Save that was pressed; the other is cleared. */
|
|
66170
|
+
const msgId=(e.submitter&&e.submitter.getAttribute("data-msg"))||"msgTop";
|
|
66171
|
+
for(const id of ["msgTop","msgBottom"])if(id!==msgId){const x=document.getElementById(id);if(x)x.textContent=""}
|
|
66006
66172
|
e.preventDefault();
|
|
66007
66173
|
const offers=lines("offers").map(l=>{
|
|
66008
66174
|
const m=l.match(/^(.*?)\\s*[.-]\\s*\\$?([\\d,.]+)\\s*(\\/mo)?$/);
|
|
@@ -66010,7 +66176,7 @@ document.getElementById("f").addEventListener("submit",async e=>{
|
|
|
66010
66176
|
return{name:m[1].trim(),priceFloor:Number(m[2].replace(/,/g,""))||undefined,cadence:m[3]?"monthly":"one-time"};
|
|
66011
66177
|
});
|
|
66012
66178
|
const pkgRead=readPackages();
|
|
66013
|
-
if(pkgRead.clash){document.getElementById(
|
|
66179
|
+
if(pkgRead.clash){document.getElementById(msgId).textContent="Two packages, "+pkgRead.clash[0]+" and "+pkgRead.clash[1]+", would be saved as one. Give one of them a different name. Nothing was saved.";return}
|
|
66014
66180
|
const staff=lines("staff").map(l=>{const [name,role,email]=l.split("\xB7").map(s=>s.trim());return{name,role:role||undefined,email:email||undefined}});
|
|
66015
66181
|
/* EMPTY clears; UNREADABLE stops the save. undefined is the "stop" signal here. */
|
|
66016
66182
|
const num=id=>{const r=readNumber(v(id));return r.clear?null:(r.bad?undefined:r.value)};
|
|
@@ -66030,8 +66196,8 @@ document.getElementById("f").addEventListener("submit",async e=>{
|
|
|
66030
66196
|
vocabulary:lines("icaVocabulary").length?lines("icaVocabulary"):null,hangouts:lines("icaHangouts").length?lines("icaHangouts"):null,villains:lines("icaVillains").length?lines("icaVillains"):null},
|
|
66031
66197
|
offer:{name:v("offerName")||null,promise:v("offerPromise")||null,price:v("offerPrice")||null,guarantee:v("offerGuarantee")||null,
|
|
66032
66198
|
valueStack:lines("offerStack").length?lines("offerStack"):null,accelerators:lines("offerAccelerators").length?lines("offerAccelerators"):null}}};
|
|
66033
|
-
/* Said beside the
|
|
66034
|
-
const m={set textContent(t){document.getElementById(
|
|
66199
|
+
/* Said beside the Save that was pressed. */
|
|
66200
|
+
const m={set textContent(t){document.getElementById(msgId).textContent=t}};
|
|
66035
66201
|
const badNum=["minMonthly","minSetup"].find(id=>body.pricing[id]===undefined);
|
|
66036
66202
|
if(badNum){m.textContent="That is not a number I can read, so nothing was saved. Check the "+(badNum==="minMonthly"?"minimum monthly":"minimum setup fee")+".";return}
|
|
66037
66203
|
m.textContent="Saving\u2026";
|
|
@@ -69043,11 +69209,11 @@ var init_closed_roots = __esm({
|
|
|
69043
69209
|
READ_RESULT_MAX_CHARS = 16e6;
|
|
69044
69210
|
OVER_BUDGET_KEY = "__secretShopperOverBudget";
|
|
69045
69211
|
overBudget = (v) => !!v && typeof v === "object" && OVER_BUDGET_KEY in v;
|
|
69046
|
-
deadline = (p, ms, what) => new Promise((
|
|
69212
|
+
deadline = (p, ms, what) => new Promise((resolve15, reject) => {
|
|
69047
69213
|
const t = setTimeout(() => reject(new Error(`${what} took longer than ${ms} ms`)), ms);
|
|
69048
69214
|
p.then((v) => {
|
|
69049
69215
|
clearTimeout(t);
|
|
69050
|
-
|
|
69216
|
+
resolve15(v);
|
|
69051
69217
|
}, (e) => {
|
|
69052
69218
|
clearTimeout(t);
|
|
69053
69219
|
reject(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -69364,10 +69530,10 @@ async function browserPass(url, opts = {}) {
|
|
|
69364
69530
|
let timer;
|
|
69365
69531
|
let expiredAt = false;
|
|
69366
69532
|
let closeOnce;
|
|
69367
|
-
const expired2 = new Promise((
|
|
69533
|
+
const expired2 = new Promise((resolve15) => {
|
|
69368
69534
|
timer = setTimeout(() => {
|
|
69369
69535
|
expiredAt = true;
|
|
69370
|
-
|
|
69536
|
+
resolve15({ ok: false, reason: `The rendered pass took longer than ${Math.round(deadlineMs / 1e3)} seconds and was stopped.` });
|
|
69371
69537
|
}, deadlineMs);
|
|
69372
69538
|
});
|
|
69373
69539
|
const work = (async () => {
|
|
@@ -70085,8 +70251,8 @@ function callDate(source, now) {
|
|
|
70085
70251
|
}
|
|
70086
70252
|
return { date: now.toISOString().slice(0, 10), fromRecording: false };
|
|
70087
70253
|
}
|
|
70088
|
-
function confirmList(
|
|
70089
|
-
return
|
|
70254
|
+
function confirmList(held2) {
|
|
70255
|
+
return held2.map((h) => {
|
|
70090
70256
|
const saved = /already has "([^"]*)" saved/.exec(h.because)?.[1];
|
|
70091
70257
|
return `${ASK.get(h.questionId) ?? h.questionId} \u2192 ${h.value} (their words: ${q2(h.quote)})${saved !== void 0 ? ` [replaces the saved "${saved}"]` : ""}`;
|
|
70092
70258
|
});
|
|
@@ -70367,8 +70533,8 @@ async function saveToContact(input, deps, gated, source) {
|
|
|
70367
70533
|
for (const c of input.confirmed ?? []) {
|
|
70368
70534
|
const id2 = String(c?.questionId), value = String(c?.value ?? "");
|
|
70369
70535
|
if (confirmedIds.has(id2)) continue;
|
|
70370
|
-
const
|
|
70371
|
-
out.confirmationsNotUsed.push({ questionId: id2, value, why:
|
|
70536
|
+
const held2 = gated.held.find((h) => h.questionId === id2);
|
|
70537
|
+
out.confirmationsNotUsed.push({ questionId: id2, value, why: held2 ? `the held answer is "${held2.value}", not "${value}". Propose "${value}" with its quote, then confirm it.` : gated.rejected.some((r) => r.questionId === id2) ? "that answer was thrown out: its quote is not in the transcript." : "no answer to this question is waiting for confirmation." });
|
|
70372
70538
|
}
|
|
70373
70539
|
const candidates = [...gated.accepted, ...gated.held.filter((h) => confirmedIds.has(h.questionId)).map(({ because: _b, ...a }) => a)];
|
|
70374
70540
|
const prior = record?.state.ans ?? {};
|
|
@@ -71412,13 +71578,13 @@ function briefFromAnswers(answers, questions, industry, briefId) {
|
|
|
71412
71578
|
if (cleared.length) brief.__cleared = cleared;
|
|
71413
71579
|
return brief;
|
|
71414
71580
|
}
|
|
71415
|
-
function getPath2(o,
|
|
71581
|
+
function getPath2(o, path67) {
|
|
71416
71582
|
let cur = o;
|
|
71417
|
-
for (const k of
|
|
71583
|
+
for (const k of path67.split(".")) cur = isRec2(cur) ? cur[k] : void 0;
|
|
71418
71584
|
return cur;
|
|
71419
71585
|
}
|
|
71420
|
-
function clearPath(o,
|
|
71421
|
-
const keys =
|
|
71586
|
+
function clearPath(o, path67) {
|
|
71587
|
+
const keys = path67.split(".");
|
|
71422
71588
|
let cur = o;
|
|
71423
71589
|
for (const k of keys.slice(0, -1)) cur = isRec2(cur) ? cur[k] : void 0;
|
|
71424
71590
|
if (isRec2(cur)) delete cur[keys[keys.length - 1]];
|
|
@@ -71551,12 +71717,12 @@ function withLocationLock(dir, locationId2, fn, opts = {}) {
|
|
|
71551
71717
|
return fn();
|
|
71552
71718
|
} finally {
|
|
71553
71719
|
if (fd !== void 0) fs52.closeSync(fd);
|
|
71554
|
-
let
|
|
71720
|
+
let held2 = "";
|
|
71555
71721
|
try {
|
|
71556
|
-
|
|
71722
|
+
held2 = fs52.readFileSync(lock, "utf8");
|
|
71557
71723
|
} catch {
|
|
71558
71724
|
}
|
|
71559
|
-
if (
|
|
71725
|
+
if (held2 === token2) fs52.rmSync(lock, { force: true });
|
|
71560
71726
|
}
|
|
71561
71727
|
}
|
|
71562
71728
|
function claimClinicPrefix(locationId2, prefix, clientKey, opts) {
|
|
@@ -72201,12 +72367,12 @@ async function resolvePlan2(creds, opts = {}) {
|
|
|
72201
72367
|
}
|
|
72202
72368
|
return { error: "Could not verify the license (offline and no valid cached attestation)." };
|
|
72203
72369
|
}
|
|
72204
|
-
async function recheckCockpitLicense(
|
|
72370
|
+
async function recheckCockpitLicense(resolve15 = resolvePlan2, opts = {}) {
|
|
72205
72371
|
const id2 = pinnedLicenseIdentity();
|
|
72206
72372
|
if (!id2) return;
|
|
72207
72373
|
const seq = beginLicenseCheck(id2);
|
|
72208
72374
|
const startedWith = readCredentials();
|
|
72209
|
-
const r = await
|
|
72375
|
+
const r = await resolve15({ ...id2 }, { persist: false }).catch(() => ({ error: "unreachable" }));
|
|
72210
72376
|
if ("error" in r) {
|
|
72211
72377
|
if (r.reason) applyLicenseVerdict(seq, id2, r.reason === "team_limit" ? { ok: false, reason: "team_limit", detail: r.error, serverTime: r.serverTime } : { ok: false, reason: r.reason, serverTime: r.serverTime }, opts);
|
|
72212
72378
|
if (licenseSessionRefusal(Date.now(), "cockpit")) cockpitLicenceRefused("licence re-check refused");
|
|
@@ -72219,6 +72385,17 @@ function assertCockpitLicenseHolds(now = Date.now()) {
|
|
|
72219
72385
|
const refusal2 = licenseSessionRefusal(now, "cockpit");
|
|
72220
72386
|
if (refusal2) throw new Error(refusal2);
|
|
72221
72387
|
}
|
|
72388
|
+
function claimRunStart() {
|
|
72389
|
+
if (activeRun2?.running || runStarting) throw new Error(RUN_IN_PROGRESS);
|
|
72390
|
+
runStarting = true;
|
|
72391
|
+
let done = false;
|
|
72392
|
+
return () => {
|
|
72393
|
+
if (!done) {
|
|
72394
|
+
done = true;
|
|
72395
|
+
runStarting = false;
|
|
72396
|
+
}
|
|
72397
|
+
};
|
|
72398
|
+
}
|
|
72222
72399
|
function takeOneTimeLine(entry, runLocationId, l) {
|
|
72223
72400
|
if (l.locationId !== runLocationId) return false;
|
|
72224
72401
|
const k = l.email.trim().toLowerCase();
|
|
@@ -72295,7 +72472,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
72295
72472
|
} catch {
|
|
72296
72473
|
runFacts = {};
|
|
72297
72474
|
}
|
|
72298
|
-
runFacts = withBuiltCalendarTeams(runFacts, readSavedPlan(rk));
|
|
72475
|
+
runFacts = withBuiltCalendarTeams(runFacts, readSavedPlan(rk), calendarTeamsFromJournal(locationId2));
|
|
72299
72476
|
} catch (e) {
|
|
72300
72477
|
context2.readback = plannedReadbacks(verificationPlan).map(({ ref, type, name: name2 }) => ({ ref, type, name: name2, status: "unreadable", detail: e instanceof Error ? e.message : String(e) }));
|
|
72301
72478
|
}
|
|
@@ -72330,7 +72507,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
72330
72507
|
const ticked = standingTicks(tasksNow, currentKeys);
|
|
72331
72508
|
const openRecipients = readBuildObligations(rk).filter((o) => o.type === "staff_pending" && o.ref.startsWith("workflow.") && !ticked.has(`${o.type}:${o.ref}`)).length;
|
|
72332
72509
|
const openHandoffs = planHandoffs.filter((h) => {
|
|
72333
|
-
const st = handoffInAccount(h, runFacts, readback, plannedUsers, openRecipients, plannedCalendars);
|
|
72510
|
+
const st = handoffInAccount(fitHandoff(h, planHandoffs, runFacts), runFacts, readback, plannedUsers, openRecipients, plannedCalendars);
|
|
72334
72511
|
const mainOpen = !(st.readable && st.done) && !ticked.has(h.ref);
|
|
72335
72512
|
const part = handoffPartId(h.ref);
|
|
72336
72513
|
return mainOpen || part !== void 0 && !ticked.has(part);
|
|
@@ -72713,7 +72890,7 @@ function mergeQueueIntakes(latest, reread, before) {
|
|
|
72713
72890
|
return { ...latest, intakeInputs: { ...latest.intakeInputs ?? {}, ...fresh } };
|
|
72714
72891
|
}
|
|
72715
72892
|
function gateReadBody(req) {
|
|
72716
|
-
return new Promise((
|
|
72893
|
+
return new Promise((resolve15) => {
|
|
72717
72894
|
let body = "", over2 = false;
|
|
72718
72895
|
req.on("data", (c) => {
|
|
72719
72896
|
if (over2) return;
|
|
@@ -72724,8 +72901,8 @@ function gateReadBody(req) {
|
|
|
72724
72901
|
req.resume?.();
|
|
72725
72902
|
}
|
|
72726
72903
|
});
|
|
72727
|
-
req.on("end", () =>
|
|
72728
|
-
req.on("error", () =>
|
|
72904
|
+
req.on("end", () => resolve15(over2 ? null : body));
|
|
72905
|
+
req.on("error", () => resolve15(null));
|
|
72729
72906
|
});
|
|
72730
72907
|
}
|
|
72731
72908
|
function readBody(req, res, max, onDone, tooLarge = "That request is too large.", sensitive = false) {
|
|
@@ -72739,10 +72916,10 @@ function readBody(req, res, max, onDone, tooLarge = "That request is too large."
|
|
|
72739
72916
|
} catch {
|
|
72740
72917
|
}
|
|
72741
72918
|
};
|
|
72742
|
-
const
|
|
72743
|
-
if (typeof
|
|
72919
|
+
const held2 = req[GATE_BODY];
|
|
72920
|
+
if (typeof held2 === "string") {
|
|
72744
72921
|
setImmediate(() => {
|
|
72745
|
-
if (
|
|
72922
|
+
if (held2.length > max) {
|
|
72746
72923
|
try {
|
|
72747
72924
|
res.writeHead(413, { "Content-Type": "application/json" });
|
|
72748
72925
|
res.end(JSON.stringify({ ok: false, error: tooLarge }));
|
|
@@ -72751,7 +72928,7 @@ function readBody(req, res, max, onDone, tooLarge = "That request is too large."
|
|
|
72751
72928
|
return;
|
|
72752
72929
|
}
|
|
72753
72930
|
try {
|
|
72754
|
-
const r = onDone(
|
|
72931
|
+
const r = onDone(held2);
|
|
72755
72932
|
if (r && typeof r.catch === "function") r.catch(fail);
|
|
72756
72933
|
} catch (e) {
|
|
72757
72934
|
fail(e);
|
|
@@ -74256,6 +74433,8 @@ function unsavedTyping(){try{
|
|
|
74256
74433
|
return [...m.querySelectorAll("input,textarea")].some(el=>textField(el)&&!el.disabled&&typedKeys.has(fieldKey(el,m))&&String(el.value||"").trim())}catch(e){return false}}
|
|
74257
74434
|
/* A write in flight arms it too: a link that leaves the page would cut it off. */
|
|
74258
74435
|
window.addEventListener("beforeunload",e=>{if(liveWrites>0||unsavedTyping()){e.preventDefault();e.returnValue=""}});
|
|
74436
|
+
/* Walk W27: the folded Start a new client stays open across redraws once the member opened it. */
|
|
74437
|
+
let ncFoldOpen=false;
|
|
74259
74438
|
function newClientForm(){const f=document.createElement("div");f.className="cds-panel ncform";
|
|
74260
74439
|
f.innerHTML="<h2 class='cds-panel-title'>Create the sub-account</h2><p>GHL Command creates sub-accounts on GoHighLevel's Agency Pro plan; on other plans you create it in GoHighLevel and paste its key into Command OS.</p>";
|
|
74261
74440
|
const g=document.createElement("div");g.className="ncgrid";
|
|
@@ -75178,7 +75357,26 @@ function capEmpty(title,text){const e=mk("div","cds-empty");e.appendChild(mk("h3
|
|
|
75178
75357
|
/* Capability jobs that read and change nothing (audit 2026-10-28 #22). */
|
|
75179
75358
|
/* Payments makes catalog products from the approved plan's package names, and nothing without them (audit #18). */
|
|
75180
75359
|
function capBuildNote(){return CAP.key==="payments"?"Uses the package names in the approved plan. With none approved, it creates nothing and says so.":""}
|
|
75181
|
-
|
|
75360
|
+
/* Walk W41: what the main button does, in plain words ("Build this for <client>" alone said nothing). */
|
|
75361
|
+
const CAP_ACTION={pipelines:"build the pipelines and stages",calendars:"build the booking calendars",forms:"build the forms",pages:"build the pages and funnels",fields:"add the fields, values and tags",staff:"check the staff and users",phone:"check the phone numbers and A2P",workflows:"build the workflows as drafts",reactivation:"build the reactivation campaign as drafts",templates:"write the email and SMS templates",broadcasts:"draft the broadcast emails","conversation-ai":"build the Conversation AI bot","voice-ai":"build the Voice AI agent",payments:"prepare the three packages in the catalog",memberships:"build the course and membership",reviews:"build the review requests as drafts",blogs:"draft the blog post",checkup:"run the checkup",contacts:"find everyone who has not booked in 90 days",integrations:"check the integrations and webhooks"};
|
|
75362
|
+
function capBuildLabel(c){return "Have Claude "+(CAP_ACTION[CAP.key]||"do this for "+c.name)}
|
|
75363
|
+
/* The main button (top and bottom alike): opens the one before-panel beside itself. */
|
|
75364
|
+
function capMainButton(c,words,busy){const b=mk("button","cds-btn cap-build",capBuildLabel(c));b.type="button";if(busy)b.disabled=true;b.onclick=()=>capAsk(c,words,b);return b}
|
|
75365
|
+
/* The W8 before-panel, one at a time, beside the button pressed; nothing runs until Yes. */
|
|
75366
|
+
function capAsk(c,words,btn){for(const x of document.querySelectorAll(".cap-ask"))x.remove();
|
|
75367
|
+
const ask=mk("div","cds-callout cap-ask");ask.setAttribute("role","group");ask.appendChild(mk("span","ttl","Before Claude starts: this works in "+c.name));
|
|
75368
|
+
ask.appendChild(mk("p","",words));const before=String(CAP.before||"").split("{client}").join(c.name);if(before)ask.appendChild(mk("p","",before));
|
|
75369
|
+
const row=mk("div","cds-actions");const yes=mk("button","cds-btn","Yes, "+capBuildLabel(c).charAt(0).toLowerCase()+capBuildLabel(c).slice(1));yes.type="button";const no=mk("button","cds-btn ghost","Cancel");no.type="button";
|
|
75370
|
+
no.onclick=()=>ask.remove();yes.onclick=async()=>{yes.disabled=no.disabled=true;yes.textContent="Starting\u2026";await capStart(c,ask)};row.appendChild(yes);row.appendChild(no);ask.appendChild(row);
|
|
75371
|
+
const host=btn.parentNode;host.insertBefore(ask,btn.nextSibling);if(ask.scrollIntoView)try{ask.scrollIntoView({block:"nearest"})}catch(e){}}
|
|
75372
|
+
async function capStart(c,ask){const tok=clientToken();
|
|
75373
|
+
const shownReads=capReadsFor===c.locationId&&capReads&&capReads.ok&&Array.isArray(capReads.fingerprints)?capReads.fingerprints:null;
|
|
75374
|
+
const r=await(await fetch("/api/run-capability",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,key:CAP.key,confirmed:true,readsShown:shownReads})})).json();
|
|
75375
|
+
ask.remove();
|
|
75376
|
+
/* Out of date (safety release): the panel shows what the account has now, and the refusal says what changed. */
|
|
75377
|
+
if(r.stale&&Array.isArray(r.reads)){if(!stillFor(tok)){alertFor(c.locationId,r.error);return}capReadsFor=c.locationId;capReads={ok:true,key:CAP.key,locationId:c.locationId,reads:r.reads,fingerprints:r.fingerprints};render(lastState,lastRun);alertFor(c.locationId,r.error);return}
|
|
75378
|
+
if(!r.ok){alertFor(c.locationId,r.error||"Could not start");return}
|
|
75379
|
+
if(!stillFor(tok))return;capRun={locationId:c.locationId};pollCap()}
|
|
75182
75380
|
/* What one read counts, in the owner's words and the right number: "1 bot", "2 bots". */
|
|
75183
75381
|
function readLabel(label,n){if(Number(n)!==1)return label;const r=((CAP&&CAP.reads)||[]).find(x=>x.label===label);return (r&&r.one)||String(label).replace(/s$/,"")}
|
|
75184
75382
|
/* One row per thing the read found: its name, and the sub line the read gives (amount, status and date; N stages). */
|
|
@@ -75201,17 +75399,10 @@ function renderCapability(m,state,run){
|
|
|
75201
75399
|
sel.setAttribute("data-switch","");sel.onchange=()=>goClient(sel.value);pick.appendChild(sel);end.appendChild(pick);
|
|
75202
75400
|
const hasBuild=CAP.key!=="intake"&&CAP.key!=="handoff"&&!CAP.noBuild;
|
|
75203
75401
|
let bb=null;
|
|
75204
|
-
|
|
75205
|
-
|
|
75206
|
-
|
|
75207
|
-
|
|
75208
|
-
const shownReads=capReadsFor===c.locationId&&capReads&&capReads.ok&&Array.isArray(capReads.fingerprints)?capReads.fingerprints:null;
|
|
75209
|
-
const r=await(await fetch("/api/run-capability",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,key:CAP.key,confirmed:true,readsShown:shownReads})})).json();
|
|
75210
|
-
/* Out of date (safety release): the panel shows what the account has now, and the refusal says what changed. */
|
|
75211
|
-
if(r.stale&&Array.isArray(r.reads)){if(!stillFor(tok)){alertFor(c.locationId,r.error);return}capReadsFor=c.locationId;capReads={ok:true,key:CAP.key,locationId:c.locationId,reads:r.reads,fingerprints:r.fingerprints};render(lastState,lastRun);alertFor(c.locationId,r.error);return}
|
|
75212
|
-
if(!r.ok){alertFor(c.locationId,r.error||"Could not start");bb.disabled=false;bb.textContent=capBuildLabel(c);return}
|
|
75213
|
-
if(!stillFor(tok))return;capRun={locationId:c.locationId};pollCap()};
|
|
75214
|
-
end.appendChild(bb);if(capBuildNote())end.appendChild(mk("p","muted cap-note",capBuildNote()))}
|
|
75402
|
+
/* Walk W41 (2026-10-29, Jerry: "it is not clear at all"): the page's main action is the plain-worded build through
|
|
75403
|
+
the member's own Claude, at the top here and again at the bottom after Ready to answer; both open the one W8
|
|
75404
|
+
before-panel beside the button pressed (never a browser dialog). */
|
|
75405
|
+
if(hasBuild){bb=capMainButton(c,words,busy);end.appendChild(bb);if(capBuildNote())end.appendChild(mk("p","muted cap-note",capBuildNote()))}
|
|
75215
75406
|
/* Walk 2026-10-28 W9: the client's first page (where Your clients leads) has its build as a button. */
|
|
75216
75407
|
if(CAP.key==="intake"){const go=mk("a","cds-btn cap-build-account","Build this client's account");end.appendChild(asClientLink(go,"/new",c.locationId))}
|
|
75217
75408
|
m.appendChild(screenHead(CAP.label,CAP.what,CAP.groupLabel,end));
|
|
@@ -75267,7 +75458,7 @@ function capReadsInto(card,c,hasBuild,foldAll){
|
|
|
75267
75458
|
const stack=mk("div","cds-stack");const several=foldAll||capReads.reads.length>1;
|
|
75268
75459
|
for(const r of capReads.reads){const block=mk("div","cap-read");
|
|
75269
75460
|
if(r.status!=="ok"){const w=mk("div","cds-callout warn");w.appendChild(mk("span","ttl",r.countOnly?(capFirst(readLabel(r.label,1))+" count not read yet"):"Could not read the "+r.label));w.appendChild(mk("span","",String(r.reason||"GoHighLevel did not answer.").replace(/^could not read: /,"")));block.appendChild(w)}
|
|
75270
|
-
else if(!r.count)block.appendChild(capEmpty("No "+r.label+" yet",hasBuild?"Use
|
|
75461
|
+
else if(!r.count)block.appendChild(capEmpty("No "+r.label+" yet",hasBuild?"Use "+capBuildLabel(c)+" above, or ask Claude yourself with the words below.":"Ask Claude yourself with the words below."));
|
|
75271
75462
|
else if(r.countOnly){const ul=mk("ul","cds-list");ul.appendChild(capRow(Number(r.count).toLocaleString("en-US")+" "+readLabel(r.label,r.count),"Counted by GoHighLevel."));block.appendChild(ul)}
|
|
75272
75463
|
else if(!(r.names||[]).length&&!(r.items||[]).length)block.appendChild(mk("p","muted",r.count+" "+readLabel(r.label,r.count)+" found; GoHighLevel returned no names for them."));
|
|
75273
75464
|
else if(several&&!foldAll&&r.count<=3){block.appendChild(mk("div","cds-panel-label",r.count+" "+readLabel(r.label,r.count)));const ul=mk("ul","cds-list");readRows(r).forEach(x=>ul.appendChild(x));block.appendChild(ul)}
|
|
@@ -75293,21 +75484,44 @@ function capCheckupPanel(last,bb){const p=panel("Last checkup",last?"What the la
|
|
|
75293
75484
|
row.appendChild(mk("span","",last.summary||(last.ok?"The checkup finished.":(last.error||"The run did not finish."))));p.appendChild(row);
|
|
75294
75485
|
if(iss.length)p.appendChild(mk("p","muted","Each one is listed under Still owed, below."));
|
|
75295
75486
|
return p}
|
|
75487
|
+
/* Walk W42: Ready to answer, from the account as read now (the page's live reads) and the intake. */
|
|
75488
|
+
function capReadiness(c,state,last){const reads=capReadsFor===c.locationId&&capReads&&capReads.ok?capReads.reads:null;
|
|
75489
|
+
const fromRead=(label,none)=>{if(!reads)return {wait:true,text:"Reading the account\u2026"};const r=reads.find(x=>x.label===label);if(!r)return {wait:true,text:"Reading the account\u2026"};
|
|
75490
|
+
if(r.status!=="ok")return {text:"Could not read: "+(r.reason||"GoHighLevel did not answer")};
|
|
75491
|
+
const names=(r.names||[]).filter(Boolean);if(!r.count)return {text:none};
|
|
75492
|
+
return {ok:true,text:names.length?names.slice(0,2).join(", ")+(r.count>2?", and "+(r.count-2)+" more":""):r.count+" in this account"}};
|
|
75493
|
+
const ans=Object.assign({},((state.intakeDrafts||{})[c.locationId]||{}).answers||{},((state.intakeInputs||{})[c.locationId]||{}).answers||{},((state.intakeCompleted||{})[c.locationId]||{}).answers||{});
|
|
75494
|
+
const raw=ans.website??ans.Website;const site=String(raw&&typeof raw==="object"?(raw.value??""):(raw??"")).trim();
|
|
75495
|
+
const said=last&&last.readiness&&last.readiness["Test conversation"];
|
|
75496
|
+
return {Website:site?{ok:true,text:site}:{text:"No website in the intake yet. Add it on step 5, Intake."},
|
|
75497
|
+
"Booking calendar":fromRead("booking calendars","No booking calendar in this account yet."),
|
|
75498
|
+
"Phone number":fromRead("phone numbers","No phone number in this account yet."),
|
|
75499
|
+
"Test conversation":said?{ok:true,text:said}:{text:"Yours to do: have one test conversation with it."}}}
|
|
75296
75500
|
/* Do it now: the words, the ways to run them, and what the last run left. */
|
|
75297
75501
|
function capDoPanel(c,state,run,words,hasBuild,busy,last){
|
|
75298
|
-
const doc=panel("Do it now","
|
|
75502
|
+
const doc=panel("Do it now",hasBuild?capBuildLabel(c):"Optional help");doc.classList.add("cap-do");
|
|
75503
|
+
/* Walk W41: asking Claude yourself is the optional path, folded to one line; on a page with no build it is the only
|
|
75504
|
+
action, open, and still labelled as optional help. */
|
|
75505
|
+
const fold=mk("details","cds-details cap-words-fold");if(!hasBuild)fold.open=true;
|
|
75506
|
+
fold.appendChild(mk("summary","",hasBuild?"Prefer to ask Claude yourself? Copy the words":"Optional help: ask Claude yourself. Copy the words"));
|
|
75299
75507
|
const q=mk("div","cds-callout say cap-words");q.appendChild(mk("span","",words));
|
|
75300
75508
|
const acts=mk("div","cds-actions");
|
|
75301
75509
|
const cp=mk("button","cds-btn ghost","Copy the words");cp.onclick=async()=>{try{await navigator.clipboard.writeText(words);cp.textContent="Copied"}catch(e){cp.textContent="Select and copy the text above"}setTimeout(()=>{cp.textContent="Copy the words"},2000)};acts.appendChild(cp);
|
|
75302
75510
|
if(CAP.hub){const h=mk("a","cds-btn ghost","Step by step in the hub");h.href=HUB+CAP.hub;h.target="_blank";h.rel="noopener";acts.appendChild(h)}
|
|
75303
|
-
q.appendChild(acts);
|
|
75511
|
+
q.appendChild(acts);fold.appendChild(q);
|
|
75304
75512
|
const drafts=DRAFT_PAGES.includes(CAP.key);
|
|
75305
75513
|
doc.appendChild(mk("p","muted cap-note",CAP.noBuild?"This one messages a real person, so it has no Build button: paste the words into your own Claude and review the complete draft before approving any send or publication.":!hasBuild?"Paste the words into your own Claude and watch it work.":busy?"Another run is in progress, and Build this wakes up when it finishes.":drafts?"This runs through your own Claude account, leaves what it makes as drafts, and sends, posts or deletes nothing.":"This runs through your own Claude account and sends, posts or deletes nothing."));
|
|
75306
75514
|
if(hasBuild&&!busy){const d=mk("details","cds-details");d.appendChild(mk("summary","","Details"));d.appendChild(mk("p","","This runs through your own Claude account, with only the tools this job needs: the words above and what it reads from this account go to Claude, and nowhere else. "+(drafts?"Workflows and messages are left as drafts for your review; nothing sends until you turn it on, and activation is approved separately. ":"")+"Nothing is sent, posted or deleted; every id is read from this account first. Or paste the words into your own Claude and watch it work."));doc.appendChild(d)}
|
|
75307
75515
|
if(["conversation-ai","voice-ai"].includes(CAP.key)){const block=mk("div","cap-sub");block.appendChild(mk("div","cds-panel-label","Ready to answer"));
|
|
75308
|
-
|
|
75309
|
-
|
|
75310
|
-
|
|
75516
|
+
/* Walk W42 (2026-10-29): the checks said "Not verified" for a calendar and number Demo-on-Demand has. Each now says
|
|
75517
|
+
what the account holds, read live like Verify (the page's own reads), or exactly what is missing. The test
|
|
75518
|
+
conversation stays the member's to do. */
|
|
75519
|
+
const checks=["Website","Booking calendar","Phone number","Test conversation"];const found=capReadiness(c,state,last);const nOk=checks.filter(l=>found[l]&&found[l].ok).length;
|
|
75520
|
+
block.appendChild(mk("p","cap-ready-sum",nOk===checks.length?"All "+checks.length+" are in place.":nOk+" of the "+checks.length+" are in place."));
|
|
75521
|
+
const dl=mk("dl","cds-kv cap-ready");for(const l of checks){dl.appendChild(mk("dt","",l));const dd=mk("dd");const f=found[l];if(f.ok)dd.textContent=f.text;else{dd.appendChild(chip(f.wait?"":"warn",f.wait?"Reading":"Missing"));dd.appendChild(document.createTextNode(" "+f.text))}dl.appendChild(dd)}block.appendChild(dl);doc.appendChild(block)}
|
|
75522
|
+
/* Walk W41: the main action again at the bottom, after what it checks; then the optional copy-the-words fold. */
|
|
75523
|
+
if(hasBuild&&CAP.key!=="checkup"){const row=mk("div","cds-actions cap-main-bottom");row.appendChild(capMainButton(c,words,busy));doc.appendChild(row)}
|
|
75524
|
+
doc.appendChild(fold);
|
|
75311
75525
|
/* The run, live */
|
|
75312
75526
|
if(run&&run.capability===CAP.key&&run.locationId===c.locationId){const d=mk("details","cds-details cap-run");d.open=Boolean(run.running);d.appendChild(mk("summary","","What Claude is doing"));
|
|
75313
75527
|
const pre=mk("pre","cds-callout cap-log",(run.lines||[]).slice(-40).join(String.fromCharCode(10))||"Starting\u2026");d.appendChild(pre);doc.appendChild(d);
|
|
@@ -75325,8 +75539,11 @@ function capDoPanel(c,state,run,words,hasBuild,busy,last){
|
|
|
75325
75539
|
checked" while step 10 said "GoHighLevel confirms 81 items. You confirmed 6 yourself."). One sentence, every surface. */
|
|
75326
75540
|
/* Walk 2026-10-28 W18: "confirms 66 \u2026 covers 75" left nine items unexplained. The rest are named by state. */
|
|
75327
75541
|
function handoffCountLine(lines,counts){const done=lines.filter(l=>l.state==="CHECKED").length,mine=(counts&&counts.ticked)||0;
|
|
75328
|
-
const waiting=lines.filter(l=>l.state==="WAITING").length
|
|
75329
|
-
|
|
75542
|
+
const waiting=lines.filter(l=>l.state==="WAITING").length;
|
|
75543
|
+
/* Walk W33: the draft automations are built; they are off until the member switches them on, not unconfirmed. */
|
|
75544
|
+
const drafts=lines.filter(l=>l.state!=="CHECKED"&&l.state!=="WAITING"&&/draft/i.test(String(l.reason||""))).length;
|
|
75545
|
+
const unread=lines.length-done-waiting-drafts;
|
|
75546
|
+
const rest=[drafts?"the "+drafts+" "+plural(drafts,"automation")+" "+(drafts===1?"is":"are")+" built and switched off until you turn "+(drafts===1?"it":"them")+" on":"",unread?unread+" "+(unread===1?"was":"were")+" built but GoHighLevel could not confirm "+(unread===1?"it":"them")+" yet":"",waiting?waiting+" "+(waiting===1?"waits":"wait")+" on a step outside the account, such as an approval":""].filter(Boolean);
|
|
75330
75547
|
return "GoHighLevel confirms "+done+" "+plural(done,"item")+". You confirmed "+mine+" yourself. The handoff document covers "+lines.length+" "+plural(lines.length,"item")+"."+(rest.length?" Of the other "+(lines.length-done)+", "+rest.join(", and ")+".":"")}
|
|
75331
75548
|
/* The Health pages' own list: what is verified and what is still owed. */
|
|
75332
75549
|
/* Counts the same jobs, the same way, as step 11 (/api/state jobCounts), and the handoff document separately in
|
|
@@ -75499,12 +75716,15 @@ function renderPage(state,run){if(state.roster)mergeRoster(state.roster);syncRev
|
|
|
75499
75716
|
if(SCREEN==="new"){m.classList.add("ncflow");const head=screenHead(HEADS.new.title,HEADS.new.sub,HEADS.new.kicker);head.querySelector(".cds-page-sub").classList.add("nc-lead");m.appendChild(head);
|
|
75500
75717
|
const choices=ROSTER.filter(c=>["client","sandbox"].includes(typeOf(state,c.locationId)));
|
|
75501
75718
|
const c0=ROSTER.find(c=>c.locationId===newClientId);
|
|
75502
|
-
{const wc=bsWaitingCard(state,run,c0?c0.locationId:"");if(wc&&(!c0||bsWhere(c0,state,run).step<3))m.appendChild(wc)}
|
|
75719
|
+
let waiting=null;{const wc=bsWaitingCard(state,run,c0?c0.locationId:"");if(wc&&(!c0||bsWhere(c0,state,run).step<3)){m.appendChild(wc);waiting=wc}}
|
|
75503
75720
|
if(!c0){
|
|
75504
|
-
|
|
75505
|
-
|
|
75506
|
-
|
|
75507
|
-
|
|
75721
|
+
/* Walk W27 (2026-10-29): with a build in progress the page led with an open Create form, and Jerry nearly filled it
|
|
75722
|
+
in. The builds in progress come first; starting a new client is folded under them. */
|
|
75723
|
+
let host=m;if(waiting){const more=document.createElement("details");more.className="cds-more nc-new-fold";more.open=ncCreating||ncFoldOpen;more.addEventListener("toggle",()=>{ncFoldOpen=more.open});const s=document.createElement("summary");s.textContent="Start a new client account instead";more.appendChild(s);m.appendChild(more);host=more}
|
|
75724
|
+
if(canCreateSubAccounts===null)host.appendChild(agencyPlanQuestion());
|
|
75725
|
+
else if(canCreateSubAccounts){host.appendChild(newClientForm());const fold=document.createElement("details");fold.className="cds-more";const title=document.createElement("summary");title.textContent="Register a sub-account you created in GoHighLevel";fold.appendChild(title);fold.appendChild(pasteKeyCard(true));host.appendChild(fold)}
|
|
75726
|
+
else{host.appendChild(pasteKeyCard());const quiet=document.createElement("p");quiet.className="muted";
|
|
75727
|
+
quiet.innerHTML="Your agency is on GoHighLevel's Agency Pro plan? <a class='cds-link' href='/agency#canCreateSubAccounts'>Turn on creating sub-accounts here</a>.";host.appendChild(quiet)}}
|
|
75508
75728
|
if(planNotice){const note=document.createElement("p");note.className="cds-callout warn";note.setAttribute("role","alert");note.textContent=planNotice;m.appendChild(note)}
|
|
75509
75729
|
if(c0&®isterNotice){const note=document.createElement("p");note.className="cds-callout info";note.setAttribute("role","status");note.textContent=registerNotice;m.appendChild(note)}
|
|
75510
75730
|
const existing=document.createElement("a");existing.href="#existing-clients";existing.className="cds-link";existing.textContent=c0?"Work on a different client":"Already registered? Pick the client";
|
|
@@ -75914,6 +76134,14 @@ function createBriefRoutes(seatToken, port, plan, registry2, fromCache, viewer =
|
|
|
75914
76134
|
return true;
|
|
75915
76135
|
}
|
|
75916
76136
|
if (url === "/brief") {
|
|
76137
|
+
if (plan === "pro" && !new URL(req.url || "/", "http://x").searchParams.get("client")) {
|
|
76138
|
+
const only = registry2?.listLocations() ?? [];
|
|
76139
|
+
if (only.length === 1) {
|
|
76140
|
+
res.writeHead(302, { Location: `/brief?client=${encodeURIComponent(only[0].locationId)}` });
|
|
76141
|
+
res.end();
|
|
76142
|
+
return true;
|
|
76143
|
+
}
|
|
76144
|
+
}
|
|
75917
76145
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
75918
76146
|
const picked = plan === "pro" ? new URL(req.url || "/", "http://x").searchParams.get("client") ?? "" : "";
|
|
75919
76147
|
const clientName = picked ? registry2?.listLocations().find((l) => l.locationId === picked)?.name : void 0;
|
|
@@ -76290,7 +76518,11 @@ async function runDashboard(argv) {
|
|
|
76290
76518
|
}
|
|
76291
76519
|
return { label: r.label, status: "ok", count: total2, names: [], items: [], countOnly: true };
|
|
76292
76520
|
}
|
|
76293
|
-
const
|
|
76521
|
+
const all = findArray(raw, r.key);
|
|
76522
|
+
const items = all && r.activeOnly ? all.filter((i) => {
|
|
76523
|
+
const o = Object(i);
|
|
76524
|
+
return o.isActive !== false && o.deleted !== true;
|
|
76525
|
+
}) : all;
|
|
76294
76526
|
if (!items) {
|
|
76295
76527
|
logQuietly(`capability read ${cap2.key}/${r.label}: unrecognised shape from ${p}`);
|
|
76296
76528
|
return { label: r.label, status: "unavailable", count: 0, names: [], reason: "could not read: the answer had a shape this page does not know" };
|
|
@@ -76413,7 +76645,7 @@ async function runDashboard(argv) {
|
|
|
76413
76645
|
}
|
|
76414
76646
|
if (req.method === "GET" && url === "/shopper") {
|
|
76415
76647
|
const accounts = currentRoster().filter((r) => readableAccount(r.locationId)).map((r) => ({ locationId: r.locationId, name: r.name })).sort((a, b) => a.name.localeCompare(b.name));
|
|
76416
|
-
const client = accounts.find((a) => a.locationId === (u.searchParams.get("client") ?? ""));
|
|
76648
|
+
const client = accounts.find((a) => a.locationId === (u.searchParams.get("client") ?? "")) ?? (accounts.length === 1 && !u.searchParams.get("client") ? accounts[0] : void 0);
|
|
76417
76649
|
const run = client ? latestRun(client.locationId) : void 0;
|
|
76418
76650
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
76419
76651
|
res.end(shopperPageHtml({ accounts, client, run, ownerEmail: readAgencyProfile().sender?.replyTo, now: Date.now() }, mayServeSeat(req, port) ? seatToken : "", SEAT_TOKEN_HEADER));
|
|
@@ -76609,6 +76841,26 @@ async function runDashboard(argv) {
|
|
|
76609
76841
|
}
|
|
76610
76842
|
const ownerEmail = typeof input.ownerEmail === "string" ? input.ownerEmail : "";
|
|
76611
76843
|
const testPhone = typeof input.testPhone === "string" && input.testPhone.trim() ? input.testPhone.trim() : void 0;
|
|
76844
|
+
const draftsTicked = liveWorkflows.filter((w) => workflowIds.includes(w.id) && (w.status ?? "").toLowerCase() === "draft");
|
|
76845
|
+
const draftRefusals = input.fastForward === true ? [] : draftsTicked.map((w) => `"${w.name}" is a draft, and a draft never runs: tick Fast-forward to test it.`);
|
|
76846
|
+
if (input.preview === true) {
|
|
76847
|
+
let refusals = draftRefusals;
|
|
76848
|
+
if (input.fastForward === true) {
|
|
76849
|
+
const rawHosts = Array.isArray(input.allowedHosts) ? input.allowedHosts.filter((h) => typeof h === "string") : [];
|
|
76850
|
+
try {
|
|
76851
|
+
const b = workflowReadConnection(registry2, c);
|
|
76852
|
+
refusals = await fastForwardRefusals({ client: c, admin: builderAdmin(b, publishCheckFor(c, b, { locationId: locationId2 })), locationId: locationId2, ownerEmail, alertUserId: typeof input.alertUserId === "string" && input.alertUserId ? input.alertUserId : void 0, workflowIds, allowedHosts: rawHosts.map(normalizeHost).filter(Boolean) });
|
|
76853
|
+
} catch (e) {
|
|
76854
|
+
refusals = [e instanceof Error ? e.message.split("\n")[0] : "The workflows could not be checked for fast-forward."];
|
|
76855
|
+
}
|
|
76856
|
+
}
|
|
76857
|
+
shopperJson(res, 200, { ok: true, preview: true, written: false, refusals });
|
|
76858
|
+
return;
|
|
76859
|
+
}
|
|
76860
|
+
if (draftRefusals.length) {
|
|
76861
|
+
shopperJson(res, 200, { ok: false, written: false, error: draftRefusals.join(" ") });
|
|
76862
|
+
return;
|
|
76863
|
+
}
|
|
76612
76864
|
if (input.fastForward === true) {
|
|
76613
76865
|
const rawHosts = Array.isArray(input.allowedHosts) ? input.allowedHosts.filter((h) => typeof h === "string") : typeof input.allowedHosts === "string" ? input.allowedHosts.split(/[\s,]+/) : [];
|
|
76614
76866
|
const allowedHosts = rawHosts.map(normalizeHost).filter(Boolean);
|
|
@@ -76989,12 +77241,13 @@ async function runDashboard(argv) {
|
|
|
76989
77241
|
}
|
|
76990
77242
|
if (req.method === "POST" && url === "/api/run-capability") {
|
|
76991
77243
|
readBody(req, res, 1e4, async (body) => {
|
|
77244
|
+
let releaseStart;
|
|
76992
77245
|
try {
|
|
76993
77246
|
const { locationId: locationId2, key: key2, confirmed, readsShown } = JSON.parse(body || "{}");
|
|
76994
77247
|
if (confirmed !== true) throw new Error("Review this action and approve it on the client page first.");
|
|
76995
77248
|
const cap2 = capabilityByKey(String(key2 ?? ""));
|
|
76996
77249
|
if (!cap2) throw new Error("no such capability");
|
|
76997
|
-
|
|
77250
|
+
releaseStart = claimRunStart();
|
|
76998
77251
|
const client = currentRoster().find((r) => r.locationId === locationId2);
|
|
76999
77252
|
if (!client) throw new Error("that account is not on the board");
|
|
77000
77253
|
if (cap2.reads.length) {
|
|
@@ -77047,6 +77300,8 @@ async function runDashboard(argv) {
|
|
|
77047
77300
|
} catch (e) {
|
|
77048
77301
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
77049
77302
|
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e) }));
|
|
77303
|
+
} finally {
|
|
77304
|
+
releaseStart?.();
|
|
77050
77305
|
}
|
|
77051
77306
|
});
|
|
77052
77307
|
return;
|
|
@@ -77412,6 +77667,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77412
77667
|
handoff: readHandoffSnapshot(c.locationId),
|
|
77413
77668
|
publication: readStatusRecord(c.locationId),
|
|
77414
77669
|
delivery: st.handoffDeliveries?.[c.locationId],
|
|
77670
|
+
// Walk W29: everything this build made across its runs (the journal), not only the last run's share.
|
|
77671
|
+
made: saved?.planId ? madeByBuild(c.locationId, saved.planId) : [],
|
|
77415
77672
|
// The two pages exist only after Verify (cockpit audit 2026-10-28 #36), and go with an undo (#43).
|
|
77416
77673
|
prepared: fs53.existsSync(localCopyPath(c.locationId)),
|
|
77417
77674
|
detailPrepared: fs53.existsSync(detailCopyPath(c.locationId)),
|
|
@@ -77428,6 +77685,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77428
77685
|
approvedAt: approval?.at,
|
|
77429
77686
|
stale: stageFreshness(saved?.inputHash, saved, approval, st.outcomes?.[`${rk}:3`], st.outcomes?.[`${rk}:4`], st.outcomes?.[`${rk}:1`]),
|
|
77430
77687
|
built: saved?.built ?? [],
|
|
77688
|
+
made: saved?.planId ? madeByBuild(profile.locationId, saved.planId) : [],
|
|
77431
77689
|
repairs: [],
|
|
77432
77690
|
handoff: null,
|
|
77433
77691
|
publication: readStatusRecord(profile.locationId),
|
|
@@ -77471,6 +77729,9 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77471
77729
|
}).filter((t) => t.text)];
|
|
77472
77730
|
})),
|
|
77473
77731
|
// Walk W15: a job the check read as done leaves the list; step 11 says what it confirmed, by name.
|
|
77732
|
+
// Walk W26 (2026-10-29): built objects the latest check found gone from the account, per client, so a signed-off
|
|
77733
|
+
// build can say so and offer Build again.
|
|
77734
|
+
builtMissing: Object.fromEntries(Object.entries(st.jobEvidence ?? {}).map(([id2, ev]) => [id2, (ev?.readback ?? []).filter((r) => r.status === "missing").length]).filter(([, n]) => n > 0)),
|
|
77474
77735
|
confirmedJobs: Object.fromEntries(Object.keys(st.manualTasks ?? {}).map((id2) => {
|
|
77475
77736
|
const shape = planShape(readSavedPlan(id2)?.plan);
|
|
77476
77737
|
return [id2, confirmedHandoffLines(mergeHandoffDefs(st.jobHandoffs?.[id2] ?? [], shape.handoffs), st.jobEvidence?.[id2]?.facts, shape.calendars)];
|
|
@@ -77632,9 +77893,10 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77632
77893
|
}
|
|
77633
77894
|
if (req.method === "POST" && url === "/api/run") {
|
|
77634
77895
|
readBody(req, res, 1e4, async (body) => {
|
|
77896
|
+
let releaseStart;
|
|
77635
77897
|
try {
|
|
77636
77898
|
const { locationId: locationId2, stage, intakeConfirmed, clientKey, intakeShown, inputHashShown, planHashShown } = JSON.parse(body);
|
|
77637
|
-
|
|
77899
|
+
releaseStart = claimRunStart();
|
|
77638
77900
|
const who2 = cockpitClient(readCockpitState(), currentRoster(), locationId2, clientKey);
|
|
77639
77901
|
const rk = who2.recordKey;
|
|
77640
77902
|
if (who2.clientKey && (stage === 0 || stage === 1)) throw new Error(SECOND_CLIENT_NO_INTAKE);
|
|
@@ -77771,6 +78033,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77771
78033
|
} catch (e) {
|
|
77772
78034
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
77773
78035
|
res.end(JSON.stringify(runRefusal(e)));
|
|
78036
|
+
} finally {
|
|
78037
|
+
releaseStart?.();
|
|
77774
78038
|
}
|
|
77775
78039
|
});
|
|
77776
78040
|
return;
|
|
@@ -77985,7 +78249,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77985
78249
|
const source = fileSource ? fileSource : args.url ? { kind: "url", url: String(args.url) } : args.instruction ? { kind: "recorder", instruction: String(args.instruction) } : { kind: "text", text: String(args.transcript ?? "") };
|
|
77986
78250
|
const toolContext = { servers: [] };
|
|
77987
78251
|
if (source.kind === "recorder") {
|
|
77988
|
-
const collect = (cmdArgs, done) => new Promise((
|
|
78252
|
+
const collect = (cmdArgs, done) => new Promise((resolve15) => {
|
|
77989
78253
|
const cmd = claudeCommand(claudeBin(), cmdArgs);
|
|
77990
78254
|
const child = (0, import_child_process5.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
77991
78255
|
let buf = "";
|
|
@@ -77994,7 +78258,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
77994
78258
|
if (settled) return;
|
|
77995
78259
|
settled = true;
|
|
77996
78260
|
child.kill("SIGKILL");
|
|
77997
|
-
|
|
78261
|
+
resolve15({ out: buf, ok });
|
|
77998
78262
|
};
|
|
77999
78263
|
child.stdout.on("data", (d) => {
|
|
78000
78264
|
buf += d.toString();
|
|
@@ -78727,6 +78991,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78727
78991
|
if (!companyId || !apiKey2) throw new Error("Agency profile unavailable");
|
|
78728
78992
|
return new GHLClient({ apiKey: apiKey2 }).get(`/companies/${encodeURIComponent(companyId)}`, { noRetry: true });
|
|
78729
78993
|
});
|
|
78994
|
+
void prepareSmallLogo(saved.logoFile);
|
|
78730
78995
|
const st = readCockpitState();
|
|
78731
78996
|
writeCockpitState({ ...st, agencyName: saved.agencyName ?? st.agencyName, agencyAccent: saved.accent ?? st.agencyAccent });
|
|
78732
78997
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -79111,11 +79376,11 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79111
79376
|
const run = () => {
|
|
79112
79377
|
const row = routeOf(req.method, url);
|
|
79113
79378
|
if (row?.write && req.method !== "GET" && req.method !== "HEAD") {
|
|
79114
|
-
const
|
|
79379
|
+
const held2 = req[GATE_BODY];
|
|
79115
79380
|
let client = new URL(req.url || "/", "http://x").searchParams.get("locationId") ?? void 0;
|
|
79116
|
-
if (!client &&
|
|
79381
|
+
if (!client && held2) {
|
|
79117
79382
|
try {
|
|
79118
|
-
const b = JSON.parse(
|
|
79383
|
+
const b = JSON.parse(held2);
|
|
79119
79384
|
const c = b.locationId ?? b.client ?? b.clientLocationId;
|
|
79120
79385
|
if (typeof c === "string") client = c;
|
|
79121
79386
|
} catch {
|
|
@@ -79136,9 +79401,9 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79136
79401
|
});
|
|
79137
79402
|
}, { brainHandoff: () => isConnected("brain") }));
|
|
79138
79403
|
try {
|
|
79139
|
-
await new Promise((
|
|
79404
|
+
await new Promise((resolve15, reject) => {
|
|
79140
79405
|
server2.once("error", reject);
|
|
79141
|
-
server2.listen(port, "127.0.0.1",
|
|
79406
|
+
server2.listen(port, "127.0.0.1", resolve15);
|
|
79142
79407
|
});
|
|
79143
79408
|
} catch (e) {
|
|
79144
79409
|
const msg3 = e?.code === "EADDRINUSE" ? `Port ${port} is busy with another program. Close it, or start Command OS on a different port.` : `Command OS could not start: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -79160,16 +79425,16 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79160
79425
|
if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
|
|
79161
79426
|
`);
|
|
79162
79427
|
});
|
|
79163
|
-
return await new Promise((
|
|
79428
|
+
return await new Promise((resolve15) => {
|
|
79164
79429
|
const stop3 = () => {
|
|
79165
79430
|
closeAllSecretChannels();
|
|
79166
|
-
server2.close(() =>
|
|
79431
|
+
server2.close(() => resolve15(0));
|
|
79167
79432
|
};
|
|
79168
79433
|
process.on("SIGINT", stop3);
|
|
79169
79434
|
process.on("SIGTERM", stop3);
|
|
79170
79435
|
});
|
|
79171
79436
|
}
|
|
79172
|
-
var import_zod94, import_node_util5, fs53, path53, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, HANDOFF_WORKFLOW, NOTHING_BOUGHT, NOTHING_BUILT_FROM_TICKS, ASSESSMENT_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_ASSESSMENT, APPROVE_NEEDS_INTAKE, APPROVE_NEEDS_BRIEF, INDUSTRY_FIRST, NOTHING_TICKED_IN_PLAN, SCOPE_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_SCOPE, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun2, buildOneTime, oneTimeView, runForTests, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, ASSESSMENT_PIPELINE_MISSING, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_READ_TRANSIENT, INTAKE_REFRESH_RETRYING, ACCOUNT_CHANGED_DURING_READ, commitWithoutRead, SNAPSHOTS_CHANGED_DURING_READ, IntakeColdError, INTAKE_ANSWERS_MISSING, INTAKE_READ_FAILED, RUN_IN_PROGRESS, BRIEF_NEEDS_INTAKE2, BUSINESS_TYPE_FOR_INDUSTRY, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, staffGateRef, BOARD_ROUTES, BOARD_TITLES, SCREEN_HEADS, NO_SUCH_PAGE, CHOOSE_CLIENT, ONE_CLIENT_ONLY, anthropicKeyWrites, SEAT_TOKEN_HEADER, notSignedInMsg, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, COCKPIT_AFTER_CANCEL_PATHS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
|
|
79437
|
+
var import_zod94, import_node_util5, fs53, path53, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, HANDOFF_WORKFLOW, NOTHING_BOUGHT, NOTHING_BUILT_FROM_TICKS, ASSESSMENT_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_ASSESSMENT, APPROVE_NEEDS_INTAKE, APPROVE_NEEDS_BRIEF, INDUSTRY_FIRST, NOTHING_TICKED_IN_PLAN, SCOPE_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_SCOPE, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun2, runStarting, buildOneTime, oneTimeView, runForTests, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, ASSESSMENT_PIPELINE_MISSING, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_READ_TRANSIENT, INTAKE_REFRESH_RETRYING, ACCOUNT_CHANGED_DURING_READ, commitWithoutRead, SNAPSHOTS_CHANGED_DURING_READ, IntakeColdError, INTAKE_ANSWERS_MISSING, INTAKE_READ_FAILED, RUN_IN_PROGRESS, BRIEF_NEEDS_INTAKE2, BUSINESS_TYPE_FOR_INDUSTRY, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, staffGateRef, BOARD_ROUTES, BOARD_TITLES, SCREEN_HEADS, NO_SUCH_PAGE, CHOOSE_CLIENT, ONE_CLIENT_ONLY, anthropicKeyWrites, SEAT_TOKEN_HEADER, notSignedInMsg, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, COCKPIT_AFTER_CANCEL_PATHS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
|
|
79173
79438
|
var init_dashboard = __esm({
|
|
79174
79439
|
"src/dashboard.ts"() {
|
|
79175
79440
|
"use strict";
|
|
@@ -79347,6 +79612,7 @@ var init_dashboard = __esm({
|
|
|
79347
79612
|
OFF_LICENSE = "This computer is no longer on your license. Free a device on your license, then restart Command OS.";
|
|
79348
79613
|
LICENSE_RECHECK_MS = 4 * 60 * 60 * 1e3;
|
|
79349
79614
|
activeRun2 = null;
|
|
79615
|
+
runStarting = false;
|
|
79350
79616
|
buildOneTime = /* @__PURE__ */ new Map();
|
|
79351
79617
|
oneTimeView = (e) => ({ locationId: e.locationId, ...e.clientKey ? { clientKey: e.clientKey } : {}, ...e.clientName ? { clientName: e.clientName } : {}, lines: Object.values(e.byEmail) });
|
|
79352
79618
|
runForTests = {
|
|
@@ -79374,7 +79640,7 @@ var init_dashboard = __esm({
|
|
|
79374
79640
|
DOC_NOT_SAVED = "That document is not saved for this client any more. Choose it again from the list, or add it again.";
|
|
79375
79641
|
NO_KEY_ON_FILE = "No key on file for this account. Register it in GHL Command first.";
|
|
79376
79642
|
PARKED_NOT_BUILT = "Template and hidden accounts are not built from Command OS. Change the account's type to Client first.";
|
|
79377
|
-
NEEDS_AGENCY_KEY = "
|
|
79643
|
+
NEEDS_AGENCY_KEY = "Your agency is not connected yet. Open Your agency, then Connections, and add your agency's key there.";
|
|
79378
79644
|
INTAKE_UNREADABLE = "Command OS could not read this client's account. Paste its key again under Reconnect this client, at the top of its steps on Build a client account.";
|
|
79379
79645
|
INTAKE_READ_TRANSIENT = "We could not reach this client's account just now. Try again in a moment.";
|
|
79380
79646
|
INTAKE_REFRESH_RETRYING = "Could not refresh just now; retrying.";
|
|
@@ -80082,7 +80348,7 @@ var init_location_switcher = __esm({
|
|
|
80082
80348
|
|
|
80083
80349
|
// src/tools/bulk-operations.ts
|
|
80084
80350
|
function delay2(ms) {
|
|
80085
|
-
return new Promise((
|
|
80351
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
80086
80352
|
}
|
|
80087
80353
|
function formatResults(op, results, total2) {
|
|
80088
80354
|
return `${op}: ${results.success} success, ${results.failed} failed out of ${total2}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
|
|
@@ -80487,7 +80753,7 @@ var init_handoff_doc = __esm({
|
|
|
80487
80753
|
|
|
80488
80754
|
// src/tools/account-export.ts
|
|
80489
80755
|
function delay3(ms) {
|
|
80490
|
-
return new Promise((
|
|
80756
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
80491
80757
|
}
|
|
80492
80758
|
function builderSectionAvailability(builderLocationId, requestedLocationId) {
|
|
80493
80759
|
if (!builderLocationId) {
|
|
@@ -81037,12 +81303,12 @@ function registerWorkflowClonerTools(server2, builderClient) {
|
|
|
81037
81303
|
const triggers = source.triggers || [];
|
|
81038
81304
|
for (const action of actions) {
|
|
81039
81305
|
if (action.id) {
|
|
81040
|
-
idMap.set(action.id,
|
|
81306
|
+
idMap.set(action.id, crypto14.randomUUID());
|
|
81041
81307
|
}
|
|
81042
81308
|
}
|
|
81043
81309
|
for (const trigger of triggers) {
|
|
81044
81310
|
if (trigger.id) {
|
|
81045
|
-
idMap.set(trigger.id,
|
|
81311
|
+
idMap.set(trigger.id, crypto14.randomUUID());
|
|
81046
81312
|
}
|
|
81047
81313
|
}
|
|
81048
81314
|
const remap = (id2) => {
|
|
@@ -81155,12 +81421,12 @@ function registerWorkflowClonerTools(server2, builderClient) {
|
|
|
81155
81421
|
}
|
|
81156
81422
|
);
|
|
81157
81423
|
}
|
|
81158
|
-
var import_zod98,
|
|
81424
|
+
var import_zod98, crypto14;
|
|
81159
81425
|
var init_workflow_cloner = __esm({
|
|
81160
81426
|
"src/tools/workflow-cloner.ts"() {
|
|
81161
81427
|
"use strict";
|
|
81162
81428
|
import_zod98 = require("zod");
|
|
81163
|
-
|
|
81429
|
+
crypto14 = __toESM(require("crypto"));
|
|
81164
81430
|
}
|
|
81165
81431
|
});
|
|
81166
81432
|
|
|
@@ -81206,13 +81472,13 @@ function contactServiceNote(payload) {
|
|
|
81206
81472
|
function registerSmartListTools(server2, builderClient) {
|
|
81207
81473
|
if (!builderClient) return;
|
|
81208
81474
|
const client = builderClient;
|
|
81209
|
-
async function smartListRequest(method,
|
|
81210
|
-
const url = `${SMARTLIST_BASE}${
|
|
81475
|
+
async function smartListRequest(method, path67, body) {
|
|
81476
|
+
const url = `${SMARTLIST_BASE}${path67}`;
|
|
81211
81477
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
81212
81478
|
const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
|
|
81213
81479
|
if (!response.ok) {
|
|
81214
81480
|
const text4 = await response.text();
|
|
81215
|
-
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${
|
|
81481
|
+
throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path67}
|
|
81216
81482
|
${text4}`);
|
|
81217
81483
|
}
|
|
81218
81484
|
const text3 = await response.text();
|
|
@@ -81457,11 +81723,11 @@ var init_smart_lists = __esm({
|
|
|
81457
81723
|
function registerReputationTools(server2, builderClient) {
|
|
81458
81724
|
if (!builderClient) return;
|
|
81459
81725
|
const client = builderClient;
|
|
81460
|
-
async function reputationRequest(method,
|
|
81461
|
-
const response = await builderRequest(client, method, `${REPUTATION_BASE}${
|
|
81726
|
+
async function reputationRequest(method, path67) {
|
|
81727
|
+
const response = await builderRequest(client, method, `${REPUTATION_BASE}${path67}`);
|
|
81462
81728
|
if (!response.ok) {
|
|
81463
81729
|
const text4 = await response.text();
|
|
81464
|
-
throw new Error(`Reputation API Error ${response.status}: ${method} ${
|
|
81730
|
+
throw new Error(`Reputation API Error ${response.status}: ${method} ${path67}
|
|
81465
81731
|
${text4}`);
|
|
81466
81732
|
}
|
|
81467
81733
|
const text3 = await response.text();
|
|
@@ -81599,11 +81865,11 @@ var init_email_campaigns = __esm({
|
|
|
81599
81865
|
function registerMembershipTools(server2, builderClient) {
|
|
81600
81866
|
if (!builderClient) return;
|
|
81601
81867
|
const client = builderClient;
|
|
81602
|
-
async function membershipRequest(
|
|
81603
|
-
const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${
|
|
81868
|
+
async function membershipRequest(path67, method = "GET", body) {
|
|
81869
|
+
const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path67}`, body ? { body } : {});
|
|
81604
81870
|
if (!response.ok) {
|
|
81605
81871
|
const text4 = await response.text();
|
|
81606
|
-
throw new Error(`Membership API Error ${response.status}: ${method} ${
|
|
81872
|
+
throw new Error(`Membership API Error ${response.status}: ${method} ${path67}
|
|
81607
81873
|
${text4}`);
|
|
81608
81874
|
}
|
|
81609
81875
|
const text3 = await response.text();
|
|
@@ -81789,7 +82055,7 @@ var init_memberships = __esm({
|
|
|
81789
82055
|
|
|
81790
82056
|
// src/tools/template-deployer.ts
|
|
81791
82057
|
function delay4(ms) {
|
|
81792
|
-
return new Promise((
|
|
82058
|
+
return new Promise((resolve15) => setTimeout(resolve15, ms));
|
|
81793
82059
|
}
|
|
81794
82060
|
function isAlreadyExistsError(message) {
|
|
81795
82061
|
return /already\s+exists?\b/i.test(message) || /duplicate/i.test(message);
|
|
@@ -81922,7 +82188,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
81922
82188
|
const locId = client.resolveLocationId(locationId2);
|
|
81923
82189
|
const safePath = validateTemplatePath(templateFile);
|
|
81924
82190
|
const template = TemplateSchema.parse(JSON.parse(fs54.readFileSync(safePath, "utf-8")));
|
|
81925
|
-
const
|
|
82191
|
+
const resolve15 = (text3) => {
|
|
81926
82192
|
if (typeof text3 !== "string") return text3;
|
|
81927
82193
|
let result = text3;
|
|
81928
82194
|
for (const [key2, value] of Object.entries(answers)) {
|
|
@@ -81935,7 +82201,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
81935
82201
|
return result;
|
|
81936
82202
|
};
|
|
81937
82203
|
const resolveObj = (obj2) => {
|
|
81938
|
-
if (typeof obj2 === "string") return
|
|
82204
|
+
if (typeof obj2 === "string") return resolve15(obj2);
|
|
81939
82205
|
if (Array.isArray(obj2)) return obj2.map(resolveObj);
|
|
81940
82206
|
if (obj2 && typeof obj2 === "object") {
|
|
81941
82207
|
const result = {};
|
|
@@ -82644,10 +82910,10 @@ async function confirm(run) {
|
|
|
82644
82910
|
async function runProbes(client, loc) {
|
|
82645
82911
|
return Promise.all(
|
|
82646
82912
|
PROBES.map((probe) => confirm(async () => {
|
|
82647
|
-
const
|
|
82913
|
+
const path67 = probe.path.replace("{loc}", loc);
|
|
82648
82914
|
const params = probe.params ? Object.fromEntries(Object.entries(probe.params).map(([k, v]) => [k, substitute(v, loc)])) : void 0;
|
|
82649
82915
|
try {
|
|
82650
|
-
const raw = await client.get(
|
|
82916
|
+
const raw = await client.get(path67, { params, noRetry: true });
|
|
82651
82917
|
const count4 = countRows(raw, probe.listKeys);
|
|
82652
82918
|
return {
|
|
82653
82919
|
key: probe.key,
|
|
@@ -85894,7 +86160,7 @@ function claudeGenerate(model = DEFAULT_MODEL, bin = "claude") {
|
|
|
85894
86160
|
return (instruction, { cwd }) => new Promise((resolveP) => {
|
|
85895
86161
|
const t0 = Date.now();
|
|
85896
86162
|
const cmd = claudeCommand(bin, ["-p", "--output-format", "json", "--max-turns", "1", "--tools", "", "--strict-mcp-config", "--model", model, ...NO_MEMBER_HOOKS]);
|
|
85897
|
-
const child = (0,
|
|
86163
|
+
const child = (0, import_node_child_process8.spawn)(cmd.command, cmd.args, {
|
|
85898
86164
|
cwd,
|
|
85899
86165
|
env: { ...process.env, CLAUDECODE: "", CLAUDE_CODE_ENTRYPOINT: "" },
|
|
85900
86166
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -86046,11 +86312,11 @@ function summariseRun(r) {
|
|
|
86046
86312
|
const copy = !r.copy ? "" : "error" in r.copy ? ` \xB7 copy NOT GRADED (${r.copy.error})` : ` \xB7 copy ${r.copy.verdict}${r.copy.firstBreakdown ? ` (first breakdown: ${r.copy.firstBreakdown})` : ""}`;
|
|
86047
86313
|
return `${r.medium} for ${r.brandSlug}: design ${r.ok ? `PASS on pass ${r.greenAt}` : "NOT GREEN"}${copy} \xB7 ${passes} \xB7 $${r.costUsd.toFixed(3)} \xB7 ${Math.round(r.ms / 1e3)}s \xB7 ${r.model ?? "model unrecorded"}${r.file ? ` \xB7 ${r.file}` : ""}${r.report ? ` \xB7 ${r.report}` : ""}`;
|
|
86048
86314
|
}
|
|
86049
|
-
var
|
|
86315
|
+
var import_node_child_process8, import_node_fs18, import_node_path16, DEFAULT_MODEL, FIRST2;
|
|
86050
86316
|
var init_run2 = __esm({
|
|
86051
86317
|
"src/command-os/design/run.ts"() {
|
|
86052
86318
|
"use strict";
|
|
86053
|
-
|
|
86319
|
+
import_node_child_process8 = require("node:child_process");
|
|
86054
86320
|
init_claude_locate();
|
|
86055
86321
|
init_result_text();
|
|
86056
86322
|
import_node_fs18 = require("node:fs");
|
|
@@ -89837,23 +90103,23 @@ function ownsForSignal(p, jobDir, installs, platform2 = process.platform, stat)
|
|
|
89837
90103
|
if (!installs.some((dir) => exeInside(p, dir, platform2, stat))) return false;
|
|
89838
90104
|
return profileInside(argvOf(p, platform2), jobDir, platform2);
|
|
89839
90105
|
}
|
|
89840
|
-
var import_node_fs19, import_node_path17,
|
|
90106
|
+
var import_node_fs19, import_node_path17, import_node_child_process9, LIST_TIMEOUT_MS, execText, execTextSync, WIN_FIELDS, LISTING, listingCommand, oneCommand, exeCommand, listingOf, bounded, argvOf, sepOf, foldText, CODE_SIGN_CLONE;
|
|
89841
90107
|
var init_ownership = __esm({
|
|
89842
90108
|
"src/command-os/prospect/ownership.ts"() {
|
|
89843
90109
|
"use strict";
|
|
89844
90110
|
import_node_fs19 = __toESM(require("node:fs"));
|
|
89845
90111
|
import_node_path17 = __toESM(require("node:path"));
|
|
89846
|
-
|
|
90112
|
+
import_node_child_process9 = require("node:child_process");
|
|
89847
90113
|
LIST_TIMEOUT_MS = 1e4;
|
|
89848
|
-
execText = (file3, args, timeoutMs) => new Promise((
|
|
89849
|
-
(0,
|
|
90114
|
+
execText = (file3, args, timeoutMs) => new Promise((resolve15) => {
|
|
90115
|
+
(0, import_node_child_process9.execFile)(file3, args, { maxBuffer: 64 << 20, windowsHide: true, timeout: Math.max(1, Math.floor(timeoutMs)), killSignal: "SIGKILL" }, (e, stdout) => {
|
|
89850
90116
|
const code = e ? typeof e.code === "number" ? e.code : null : 0;
|
|
89851
|
-
|
|
90117
|
+
resolve15({ code, stdout: String(stdout ?? "") });
|
|
89852
90118
|
});
|
|
89853
90119
|
});
|
|
89854
90120
|
execTextSync = (file3, args, timeoutMs) => {
|
|
89855
90121
|
try {
|
|
89856
|
-
return { code: 0, stdout: String((0,
|
|
90122
|
+
return { code: 0, stdout: String((0, import_node_child_process9.execFileSync)(file3, args, { encoding: "utf8", timeout: Math.max(1, Math.floor(timeoutMs)), killSignal: "SIGKILL", maxBuffer: 64 << 20, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] })) };
|
|
89857
90123
|
} catch {
|
|
89858
90124
|
return { code: null, stdout: "" };
|
|
89859
90125
|
}
|
|
@@ -89988,12 +90254,12 @@ async function killJob(child, marker, o = {}) {
|
|
|
89988
90254
|
return { killed: [...killed], remaining: remaining.map((p) => p.pid), failures, confirmed: clear && rootGone };
|
|
89989
90255
|
}
|
|
89990
90256
|
function shopperSlot() {
|
|
89991
|
-
let
|
|
90257
|
+
let held2 = false;
|
|
89992
90258
|
for (const j of liveJobs) {
|
|
89993
90259
|
if (j.stuck) return "stuck";
|
|
89994
|
-
if (j.held)
|
|
90260
|
+
if (j.held) held2 = true;
|
|
89995
90261
|
}
|
|
89996
|
-
return
|
|
90262
|
+
return held2 ? "held" : "free";
|
|
89997
90263
|
}
|
|
89998
90264
|
function killJobsNow(platform2 = process.platform) {
|
|
89999
90265
|
if (!liveJobs.size) return [];
|
|
@@ -90061,7 +90327,7 @@ async function runIsolated(input, o = {}) {
|
|
|
90061
90327
|
const jobDir = import_node_fs20.default.mkdtempSync(import_node_path18.default.join(import_node_fs20.default.realpathSync(import_node_os2.default.tmpdir()), "ghl-shopper-job-"));
|
|
90062
90328
|
const marker = jobMarker(jobDir, platform2);
|
|
90063
90329
|
o.onJob?.({ dir: jobDir, marker });
|
|
90064
|
-
const child = (0,
|
|
90330
|
+
const child = (0, import_node_child_process10.spawn)(process.execPath, [`--max-old-space-size=${o.maxOldSpaceMb ?? CHILD_MAX_OLD_SPACE_MB}`, o.entry ?? defaultEntry(), ...o.childArg ? [o.childArg] : []], {
|
|
90065
90331
|
stdio: ["pipe", "ignore", "pipe", "pipe", "pipe", "pipe"],
|
|
90066
90332
|
// The job's folder is its temp folder, on every platform (Windows reads TEMP/TMP), so Chrome's profile is in it.
|
|
90067
90333
|
env: { ...o.env ?? childEnv(), TMPDIR: jobDir, TMP: jobDir, TEMP: jobDir },
|
|
@@ -90290,7 +90556,7 @@ async function sweepStaleJobFolders(o = {}) {
|
|
|
90290
90556
|
}
|
|
90291
90557
|
return removed;
|
|
90292
90558
|
}
|
|
90293
|
-
var import_node_fs20, import_node_os2, import_node_path18, import_node_stream,
|
|
90559
|
+
var import_node_fs20, import_node_os2, import_node_path18, import_node_stream, import_node_child_process10, JOB_DEADLINE_MS, CHILD_MAX_OLD_SPACE_MB, TREE_MEMORY_MAX_BYTES, RESULT_MAX_BYTES, STDERR_PASS_BYTES, STDERR_TAIL_BYTES, STDERR_SCAN_BYTES, OUT_OF_MEMORY, NC_TIME_LIMIT, NC_MEMORY_LIMIT, BLIND_LOG, NC_ENDED, CHILD_ENV_KEYS, markerDir, CHANNEL_EXE, isMain, sleep5, ROOT_WAIT_MS, liveJobs, CLEANUP_RETRY_MS, CLEANUP_HOLD_MS, HELD_LOG, STUCK_LOG, RELEASED_LOG, exitHookSet, FOLDER_WAIT_MS, FOLDER_LEFT_LOG, STALE_AFTER_MS;
|
|
90294
90560
|
var init_isolate = __esm({
|
|
90295
90561
|
"src/command-os/prospect/isolate.ts"() {
|
|
90296
90562
|
"use strict";
|
|
@@ -90298,7 +90564,7 @@ var init_isolate = __esm({
|
|
|
90298
90564
|
import_node_os2 = __toESM(require("node:os"));
|
|
90299
90565
|
import_node_path18 = __toESM(require("node:path"));
|
|
90300
90566
|
import_node_stream = require("node:stream");
|
|
90301
|
-
|
|
90567
|
+
import_node_child_process10 = require("node:child_process");
|
|
90302
90568
|
init_run3();
|
|
90303
90569
|
init_ownership();
|
|
90304
90570
|
init_ownership();
|
|
@@ -90950,14 +91216,14 @@ function buildReplyResponse(full, mode, locationId2) {
|
|
|
90950
91216
|
function readBuildDetail(locationId2, detail, budget = BUILD_REPLY_BUDGET) {
|
|
90951
91217
|
let full = lastFull.get(keyOf5(locationId2, detail.of, detail.clientKey));
|
|
90952
91218
|
if (!full && !detail.clientKey) {
|
|
90953
|
-
const
|
|
90954
|
-
if (
|
|
90955
|
-
if (
|
|
91219
|
+
const held2 = [...lastFull.keys()].filter((k) => k.startsWith(`${locationId2}|`) && k.endsWith(`|${detail.of}`));
|
|
91220
|
+
if (held2.length > 1) return { ok: false, error: `Replies for more than one client in this account are held. Pass detail.clientKey (the clientKey on the reply you are reading).` };
|
|
91221
|
+
if (held2.length === 1) full = lastFull.get(held2[0]);
|
|
90956
91222
|
}
|
|
90957
91223
|
if (!full) return { ok: false, error: `No ${detail.of} reply from apply_build_plan is held for this ${detail.clientKey ? "client" : "account"} in this session. Run apply_build_plan with mode:"${detail.of}" first.` };
|
|
90958
|
-
const
|
|
91224
|
+
const path67 = detail.section.split(".");
|
|
90959
91225
|
let value = full;
|
|
90960
|
-
for (const p of
|
|
91226
|
+
for (const p of path67) value = obj(value)[p];
|
|
90961
91227
|
if (value === void 0) return { ok: false, error: `The ${detail.of} reply has no section "${detail.section}".`, sections: Object.keys(full) };
|
|
90962
91228
|
const room = budget - 600;
|
|
90963
91229
|
let pages;
|
|
@@ -91193,6 +91459,119 @@ var init_publish_report = __esm({
|
|
|
91193
91459
|
}
|
|
91194
91460
|
});
|
|
91195
91461
|
|
|
91462
|
+
// src/build-lock.ts
|
|
91463
|
+
function pidAlive2(pid) {
|
|
91464
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
91465
|
+
try {
|
|
91466
|
+
process.kill(pid, 0);
|
|
91467
|
+
return true;
|
|
91468
|
+
} catch (e) {
|
|
91469
|
+
return e.code === "EPERM";
|
|
91470
|
+
}
|
|
91471
|
+
}
|
|
91472
|
+
function buildLockPath(locationId2, base = appDataDir()) {
|
|
91473
|
+
return path63.join(base, "build-locks", `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.lock`);
|
|
91474
|
+
}
|
|
91475
|
+
function claimBuildLock(locationId2, base) {
|
|
91476
|
+
const lock = buildLockPath(locationId2, base);
|
|
91477
|
+
fs63.mkdirSync(path63.dirname(lock), { recursive: true, mode: 448 });
|
|
91478
|
+
const mine = JSON.stringify({ pid: process.pid, token: crypto15.randomUUID(), at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
91479
|
+
for (let attempt2 = 0; attempt2 < 3; attempt2++) {
|
|
91480
|
+
try {
|
|
91481
|
+
const fd = fs63.openSync(lock, "wx", 384);
|
|
91482
|
+
try {
|
|
91483
|
+
fs63.writeSync(fd, mine);
|
|
91484
|
+
} finally {
|
|
91485
|
+
fs63.closeSync(fd);
|
|
91486
|
+
}
|
|
91487
|
+
held.set(lock, mine);
|
|
91488
|
+
return () => {
|
|
91489
|
+
if (held.get(lock) === mine) held.delete(lock);
|
|
91490
|
+
try {
|
|
91491
|
+
if (fs63.readFileSync(lock, "utf8") === mine) fs63.unlinkSync(lock);
|
|
91492
|
+
} catch {
|
|
91493
|
+
}
|
|
91494
|
+
};
|
|
91495
|
+
} catch (e) {
|
|
91496
|
+
if (e.code !== "EEXIST") throw e;
|
|
91497
|
+
let seen = "";
|
|
91498
|
+
let age = 0;
|
|
91499
|
+
try {
|
|
91500
|
+
seen = fs63.readFileSync(lock, "utf8");
|
|
91501
|
+
age = Date.now() - fs63.statSync(lock).mtimeMs;
|
|
91502
|
+
} catch {
|
|
91503
|
+
continue;
|
|
91504
|
+
}
|
|
91505
|
+
let pid = 0;
|
|
91506
|
+
try {
|
|
91507
|
+
pid = Number(JSON.parse(seen).pid);
|
|
91508
|
+
} catch {
|
|
91509
|
+
}
|
|
91510
|
+
const stale = pid > 0 && !pidAlive2(pid) || age > BUILD_LOCK_STALE_MS;
|
|
91511
|
+
if (!stale) return null;
|
|
91512
|
+
if (!takeOver(lock, seen)) return null;
|
|
91513
|
+
}
|
|
91514
|
+
}
|
|
91515
|
+
return null;
|
|
91516
|
+
}
|
|
91517
|
+
function takeOver(lock, seen) {
|
|
91518
|
+
const mutex = `${lock}.takeover`;
|
|
91519
|
+
let fd;
|
|
91520
|
+
try {
|
|
91521
|
+
fd = fs63.openSync(mutex, "wx", 384);
|
|
91522
|
+
} catch (e) {
|
|
91523
|
+
if (e.code !== "EEXIST") throw e;
|
|
91524
|
+
try {
|
|
91525
|
+
if (Date.now() - fs63.statSync(mutex).mtimeMs > TAKEOVER_STALE_MS) fs63.unlinkSync(mutex);
|
|
91526
|
+
} catch {
|
|
91527
|
+
}
|
|
91528
|
+
return false;
|
|
91529
|
+
}
|
|
91530
|
+
try {
|
|
91531
|
+
try {
|
|
91532
|
+
if (fs63.readFileSync(lock, "utf8") === seen) fs63.unlinkSync(lock);
|
|
91533
|
+
} catch {
|
|
91534
|
+
}
|
|
91535
|
+
} finally {
|
|
91536
|
+
fs63.closeSync(fd);
|
|
91537
|
+
try {
|
|
91538
|
+
fs63.unlinkSync(mutex);
|
|
91539
|
+
} catch {
|
|
91540
|
+
}
|
|
91541
|
+
}
|
|
91542
|
+
return true;
|
|
91543
|
+
}
|
|
91544
|
+
function touchBuildLock(locationId2, base) {
|
|
91545
|
+
const lock = buildLockPath(locationId2, base);
|
|
91546
|
+
const mine = held.get(lock);
|
|
91547
|
+
if (!mine) return;
|
|
91548
|
+
try {
|
|
91549
|
+
if (fs63.readFileSync(lock, "utf8") !== mine) {
|
|
91550
|
+
held.delete(lock);
|
|
91551
|
+
return;
|
|
91552
|
+
}
|
|
91553
|
+
const now = /* @__PURE__ */ new Date();
|
|
91554
|
+
fs63.utimesSync(lock, now, now);
|
|
91555
|
+
} catch {
|
|
91556
|
+
}
|
|
91557
|
+
}
|
|
91558
|
+
function buildRunningRefusal(accountName2) {
|
|
91559
|
+
return `A build is already running for ${accountName2}. Nothing was written by this call. Wait for that build's answer before running Build again; calling it again while it runs would create everything twice.`;
|
|
91560
|
+
}
|
|
91561
|
+
var crypto15, fs63, path63, BUILD_LOCK_STALE_MS, held, TAKEOVER_STALE_MS;
|
|
91562
|
+
var init_build_lock = __esm({
|
|
91563
|
+
"src/build-lock.ts"() {
|
|
91564
|
+
"use strict";
|
|
91565
|
+
crypto15 = __toESM(require("node:crypto"));
|
|
91566
|
+
fs63 = __toESM(require("node:fs"));
|
|
91567
|
+
path63 = __toESM(require("node:path"));
|
|
91568
|
+
init_credentials_store();
|
|
91569
|
+
BUILD_LOCK_STALE_MS = 20 * 6e4;
|
|
91570
|
+
held = /* @__PURE__ */ new Map();
|
|
91571
|
+
TAKEOVER_STALE_MS = 1e4;
|
|
91572
|
+
}
|
|
91573
|
+
});
|
|
91574
|
+
|
|
91196
91575
|
// src/intake-to-build/revert.ts
|
|
91197
91576
|
function plainDate(iso3, timeZone) {
|
|
91198
91577
|
const d = new Date(iso3);
|
|
@@ -91297,7 +91676,7 @@ function planRevert(record, opts) {
|
|
|
91297
91676
|
function errText(e) {
|
|
91298
91677
|
return e instanceof Error ? e.message : String(e);
|
|
91299
91678
|
}
|
|
91300
|
-
async function executeRevert(plan, deps, wait = (ms) => new Promise((
|
|
91679
|
+
async function executeRevert(plan, deps, wait = (ms) => new Promise((resolve15) => setTimeout(resolve15, ms))) {
|
|
91301
91680
|
const removed = [];
|
|
91302
91681
|
const refused = [];
|
|
91303
91682
|
const attempted = [];
|
|
@@ -92971,8 +93350,8 @@ async function executeBackbone(plan, rawDeps, opts = {}) {
|
|
|
92971
93350
|
calendarPending.push({ workflowRef: wf.ref, workflowName: wf.name, calendarRef: g.calendarRef, calendarName: g.calendarName, steps: g.steps, status: "pending" });
|
|
92972
93351
|
}
|
|
92973
93352
|
const waitsOnCalendar = expansion.calendarPending.length > 0;
|
|
92974
|
-
const
|
|
92975
|
-
if (
|
|
93353
|
+
const held2 = opts.heldWorkflowGates?.get(wf.ref);
|
|
93354
|
+
if (held2?.length) manual.push({ ref: wf.ref, type: "workflow-publish", name: wf.name, reason: heldWorkflowReason(wf.name, held2) });
|
|
92976
93355
|
if (opts.publishWorkflows && !opts.gatedWorkflowRefs?.has(wf.ref) && waitsOnCalendar) {
|
|
92977
93356
|
manual.push({ ref: wf.ref, type: "workflow-publish", name: wf.name, reason: `[${wf.name}] left as a draft and NOT switched on: it waits on a calendar that does not exist yet. Switch it on only after the calendar is picked on its trigger.` });
|
|
92978
93357
|
}
|
|
@@ -93543,13 +93922,13 @@ ${text4}`);
|
|
|
93543
93922
|
};
|
|
93544
93923
|
}
|
|
93545
93924
|
function makePipelineApi(builderClient) {
|
|
93546
|
-
return async (method,
|
|
93547
|
-
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${
|
|
93925
|
+
return async (method, path67, body) => {
|
|
93926
|
+
const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path67}`;
|
|
93548
93927
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
93549
93928
|
const response = await builderRequest(builderClient, method, url, sendsBody ? { body } : {});
|
|
93550
93929
|
if (!response.ok) {
|
|
93551
93930
|
const text4 = await response.text();
|
|
93552
|
-
throw new Error(`Pipeline API ${response.status}: ${method} ${
|
|
93931
|
+
throw new Error(`Pipeline API ${response.status}: ${method} ${path67}
|
|
93553
93932
|
${text4}`);
|
|
93554
93933
|
}
|
|
93555
93934
|
const text3 = await response.text();
|
|
@@ -93562,8 +93941,8 @@ ${text4}`);
|
|
|
93562
93941
|
};
|
|
93563
93942
|
}
|
|
93564
93943
|
function makeFunnelApi(builderClient) {
|
|
93565
|
-
return async (method,
|
|
93566
|
-
const url = `https://backend.leadconnectorhq.com/funnels${
|
|
93944
|
+
return async (method, path67, body) => {
|
|
93945
|
+
const url = `https://backend.leadconnectorhq.com/funnels${path67}`;
|
|
93567
93946
|
const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
|
|
93568
93947
|
const response = await builderRequest(builderClient, method, url, {
|
|
93569
93948
|
...sendsBody ? { body } : {},
|
|
@@ -93576,7 +93955,7 @@ function makeFunnelApi(builderClient) {
|
|
|
93576
93955
|
});
|
|
93577
93956
|
if (!response.ok) {
|
|
93578
93957
|
const text4 = await response.text();
|
|
93579
|
-
throw new Error(`Funnel API ${response.status}: ${method} ${
|
|
93958
|
+
throw new Error(`Funnel API ${response.status}: ${method} ${path67}
|
|
93580
93959
|
${text4.slice(0, 300)}`);
|
|
93581
93960
|
}
|
|
93582
93961
|
const text3 = await response.text();
|
|
@@ -94081,7 +94460,7 @@ function createdId(response, key2) {
|
|
|
94081
94460
|
}
|
|
94082
94461
|
function runningStageFor(locationId2) {
|
|
94083
94462
|
try {
|
|
94084
|
-
const st = JSON.parse(
|
|
94463
|
+
const st = JSON.parse(fs64.readFileSync(path64.join(appDataDir(), "cockpit-state.json"), "utf8"));
|
|
94085
94464
|
const clients = st && typeof st === "object" ? Reflect.get(st, "clients") : void 0;
|
|
94086
94465
|
const entry = clients && typeof clients === "object" ? Reflect.get(clients, locationId2) : void 0;
|
|
94087
94466
|
const stages = entry && typeof entry === "object" ? Reflect.get(entry, "stages") : void 0;
|
|
@@ -94158,6 +94537,27 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
94158
94537
|
clientKey: import_zod120.z.string().optional().describe(`Which client in this sub-account the build belongs to, when the sub-account holds more than one (lowercase letters, digits, hyphens, e.g. "qpt"). The saved plan, review scope, approval, obligations and progress for this client are kept apart from the sub-account's default client, so a second clinic's build never overwrites the first one's records. Omit for the sub-account's default client (every build before this existed). Defaults to the clinicPrefix in lower case when a prefix is given.`)
|
|
94159
94538
|
};
|
|
94160
94539
|
const buildPlanArgs = import_zod120.z.object(buildPlanSchema2);
|
|
94540
|
+
const runLockedBuildPlan = async (args) => {
|
|
94541
|
+
if ((args.mode ?? "dry_run") !== "execute") return runBuildPlan(args);
|
|
94542
|
+
const locationId2 = client.resolveLocationId();
|
|
94543
|
+
const release = claimBuildLock(locationId2);
|
|
94544
|
+
if (!release) {
|
|
94545
|
+
let name2 = locationId2;
|
|
94546
|
+
try {
|
|
94547
|
+
name2 = (await readLocationBasics(client, locationId2)).name || locationId2;
|
|
94548
|
+
} catch {
|
|
94549
|
+
}
|
|
94550
|
+
return jsonResponse({ ok: false, phase: "build", buildRunning: true, error: buildRunningRefusal(name2) });
|
|
94551
|
+
}
|
|
94552
|
+
const beat = setInterval(() => touchBuildLock(locationId2), 6e4);
|
|
94553
|
+
beat.unref?.();
|
|
94554
|
+
try {
|
|
94555
|
+
return await runBuildPlan(args);
|
|
94556
|
+
} finally {
|
|
94557
|
+
clearInterval(beat);
|
|
94558
|
+
release();
|
|
94559
|
+
}
|
|
94560
|
+
};
|
|
94161
94561
|
const runBuildPlan = async ({ plan: planArgIn, fromPreset, brief: briefArg, preset: presetArg, copy: copyArg, useSavedPlan, replaceSavedPlan, mode, locationId: locationId2, metHandoffs, publishWorkflows, textTemplates, onConflict, clinicPrefix: clinicPrefixArg, clientKey: clientKeyArg, acknowledgeSharedWorkflows }, persistPreview = false) => {
|
|
94162
94562
|
try {
|
|
94163
94563
|
const resolvedMode = mode ?? "dry_run";
|
|
@@ -94555,6 +94955,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
94555
94955
|
const record = (item) => {
|
|
94556
94956
|
try {
|
|
94557
94957
|
appendBuildObject({ runId, locationId: activeLocation, item, parents: journalParents });
|
|
94958
|
+
touchBuildLock(activeLocation);
|
|
94558
94959
|
} catch (e) {
|
|
94559
94960
|
throw new JournalWriteError(e instanceof Error ? e.message : String(e), { ref: item.ref, type: item.type, realId: item.realId });
|
|
94560
94961
|
}
|
|
@@ -94820,7 +95221,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
94820
95221
|
clientKey: import_zod120.z.string().optional().describe("The clientKey on the reply being read, when it carries one (two clients in one sub-account keep separate replies).")
|
|
94821
95222
|
}).optional().describe("READ ONLY, builds nothing: when a reply was shortened to fit (it carries replyTrimmed), read one left-out section of that full reply, a page at a time. Pass detail alone, with no other argument.")
|
|
94822
95223
|
},
|
|
94823
|
-
({ detail, ...args }) => detail ? Promise.resolve({ content: [{ type: "text", text: JSON.stringify(readBuildDetail(client.resolveLocationId(), detail)) }] }) :
|
|
95224
|
+
({ detail, ...args }) => detail ? Promise.resolve({ content: [{ type: "text", text: JSON.stringify(readBuildDetail(client.resolveLocationId(), detail)) }] }) : runLockedBuildPlan(args)
|
|
94824
95225
|
);
|
|
94825
95226
|
const withClaimWarnings = (r) => r.plan ? { ...r, warnings: [...r.warnings, ...claimWarningsForPlan(r.plan)] } : r;
|
|
94826
95227
|
server2.tool(
|
|
@@ -95300,7 +95701,7 @@ function intakeFormMatches(expected, actual) {
|
|
|
95300
95701
|
const keys = ["id", "tag", "type", "hiddenFieldQueryKey", "required", "label", "options", "picklistOptions"];
|
|
95301
95702
|
return expected.every((field2, i) => keys.every((key2) => field2[key2] === void 0 || JSON.stringify(field2[key2]) === JSON.stringify(actual[i]?.[key2])));
|
|
95302
95703
|
}
|
|
95303
|
-
var
|
|
95704
|
+
var fs64, path64, import_zod120, buildNet, customFieldItemSchema, sleep7, NO_SELECTION_SCOPE, NOT_CONFIRMED_SCOPE, WHOLE_PRESET_NOT_KEPT, workflowInventorySchema, notConfirmedAsk, STAGE_LABELS;
|
|
95304
95705
|
var init_intake_to_build = __esm({
|
|
95305
95706
|
"src/tools/intake-to-build.ts"() {
|
|
95306
95707
|
"use strict";
|
|
@@ -95309,8 +95710,8 @@ var init_intake_to_build = __esm({
|
|
|
95309
95710
|
init_build_contract();
|
|
95310
95711
|
init_build_obligations();
|
|
95311
95712
|
init_allowed_writes();
|
|
95312
|
-
|
|
95313
|
-
|
|
95713
|
+
fs64 = __toESM(require("node:fs"));
|
|
95714
|
+
path64 = __toESM(require("node:path"));
|
|
95314
95715
|
init_credentials_store();
|
|
95315
95716
|
import_zod120 = require("zod");
|
|
95316
95717
|
init_workflow_builder_client();
|
|
@@ -95334,6 +95735,7 @@ var init_intake_to_build = __esm({
|
|
|
95334
95735
|
init_brief2();
|
|
95335
95736
|
init_plan_store();
|
|
95336
95737
|
init_jobs();
|
|
95738
|
+
init_build_lock();
|
|
95337
95739
|
init_client_record();
|
|
95338
95740
|
init_clinic_prefix();
|
|
95339
95741
|
init_clinic_prefix_registry();
|
|
@@ -95427,7 +95829,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env2 = process
|
|
|
95427
95829
|
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.` }));
|
|
95428
95830
|
}
|
|
95429
95831
|
const reader = token2 ? new GHLClient({ apiKey: token2.apiKey, locationId: loc }) : client;
|
|
95430
|
-
return refuseUnseenFindings2((
|
|
95832
|
+
return refuseUnseenFindings2((path67, params) => reader.get(path67, params ? { params } : void 0), loc, findings);
|
|
95431
95833
|
}, () => client.defaultLocationId, clientScope);
|
|
95432
95834
|
registerClientEngagementTools(wrap(CLIENT_ENGAGEMENTS_MODULE), registry2, clientScope);
|
|
95433
95835
|
registerAuditReportTools(wrap(AUDIT_REPORT_MODULE));
|
|
@@ -95746,7 +96148,7 @@ function parseArgs3(argv) {
|
|
|
95746
96148
|
function openInBrowser(file3) {
|
|
95747
96149
|
const cmd = process.platform === "darwin" ? ["open", file3] : process.platform === "win32" ? ["cmd", "/c", "start", "", file3] : ["xdg-open", file3];
|
|
95748
96150
|
try {
|
|
95749
|
-
(0,
|
|
96151
|
+
(0, import_node_child_process11.spawn)(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true }).unref();
|
|
95750
96152
|
} catch {
|
|
95751
96153
|
}
|
|
95752
96154
|
}
|
|
@@ -95834,13 +96236,13 @@ async function runDesignCli(argv, io = { out: (s) => process.stdout.write(s + "\
|
|
|
95834
96236
|
}
|
|
95835
96237
|
return r.ok ? 0 : 1;
|
|
95836
96238
|
}
|
|
95837
|
-
var import_zod122,
|
|
96239
|
+
var import_zod122, import_node_child_process11, import_node_path19, import_node_os3, USAGE2;
|
|
95838
96240
|
var init_cli2 = __esm({
|
|
95839
96241
|
"src/command-os/design/cli.ts"() {
|
|
95840
96242
|
"use strict";
|
|
95841
96243
|
import_zod122 = require("zod");
|
|
95842
96244
|
init_brief_validation();
|
|
95843
|
-
|
|
96245
|
+
import_node_child_process11 = require("node:child_process");
|
|
95844
96246
|
import_node_path19 = require("node:path");
|
|
95845
96247
|
import_node_os3 = require("node:os");
|
|
95846
96248
|
init_run2();
|
|
@@ -95868,8 +96270,8 @@ __export(index_exports, {
|
|
|
95868
96270
|
});
|
|
95869
96271
|
module.exports = __toCommonJS(index_exports);
|
|
95870
96272
|
var dotenv2 = __toESM(require("dotenv"));
|
|
95871
|
-
var
|
|
95872
|
-
var
|
|
96273
|
+
var path66 = __toESM(require("path"));
|
|
96274
|
+
var fs66 = __toESM(require("fs"));
|
|
95873
96275
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
95874
96276
|
init_version_compare();
|
|
95875
96277
|
init_ghl_client();
|
|
@@ -95916,8 +96318,8 @@ var import_zod121 = require("zod");
|
|
|
95916
96318
|
|
|
95917
96319
|
// src/skill-installer.ts
|
|
95918
96320
|
var import_node_crypto34 = require("node:crypto");
|
|
95919
|
-
var
|
|
95920
|
-
var
|
|
96321
|
+
var fs65 = __toESM(require("node:fs"));
|
|
96322
|
+
var path65 = __toESM(require("node:path"));
|
|
95921
96323
|
var os11 = __toESM(require("node:os"));
|
|
95922
96324
|
function sha2563(buf) {
|
|
95923
96325
|
return (0, import_node_crypto34.createHash)("sha256").update(buf).digest("hex");
|
|
@@ -95932,9 +96334,9 @@ function isOlderRelease(candidate, installed) {
|
|
|
95932
96334
|
return false;
|
|
95933
96335
|
}
|
|
95934
96336
|
function bundledSkillsDir(baseDir) {
|
|
95935
|
-
const candidate =
|
|
96337
|
+
const candidate = path65.resolve(baseDir, "..", "skills");
|
|
95936
96338
|
try {
|
|
95937
|
-
return
|
|
96339
|
+
return fs65.statSync(candidate).isDirectory() ? candidate : null;
|
|
95938
96340
|
} catch {
|
|
95939
96341
|
return null;
|
|
95940
96342
|
}
|
|
@@ -95942,10 +96344,10 @@ function bundledSkillsDir(baseDir) {
|
|
|
95942
96344
|
function listFiles(root) {
|
|
95943
96345
|
const out = [];
|
|
95944
96346
|
const walk2 = (dir) => {
|
|
95945
|
-
for (const entry of
|
|
95946
|
-
const full =
|
|
96347
|
+
for (const entry of fs65.readdirSync(dir, { withFileTypes: true })) {
|
|
96348
|
+
const full = path65.join(dir, entry.name);
|
|
95947
96349
|
if (entry.isDirectory()) walk2(full);
|
|
95948
|
-
else if (entry.isFile()) out.push(
|
|
96350
|
+
else if (entry.isFile()) out.push(path65.relative(root, full));
|
|
95949
96351
|
}
|
|
95950
96352
|
};
|
|
95951
96353
|
walk2(root);
|
|
@@ -95953,7 +96355,7 @@ function listFiles(root) {
|
|
|
95953
96355
|
}
|
|
95954
96356
|
function readMarker(markerPath) {
|
|
95955
96357
|
try {
|
|
95956
|
-
const parsed2 = JSON.parse(
|
|
96358
|
+
const parsed2 = JSON.parse(fs65.readFileSync(markerPath, "utf8"));
|
|
95957
96359
|
return parsed2 && typeof parsed2 === "object" && parsed2.files ? parsed2 : null;
|
|
95958
96360
|
} catch {
|
|
95959
96361
|
return null;
|
|
@@ -95961,7 +96363,7 @@ function readMarker(markerPath) {
|
|
|
95961
96363
|
}
|
|
95962
96364
|
function defaultSkillsDir(env2 = process.env) {
|
|
95963
96365
|
const dir = env2.CLAUDE_CONFIG_DIR?.trim();
|
|
95964
|
-
return dir ?
|
|
96366
|
+
return dir ? path65.join(dir, "skills") : path65.join(os11.homedir(), ".claude", "skills");
|
|
95965
96367
|
}
|
|
95966
96368
|
function installBundledSkills(opts) {
|
|
95967
96369
|
const result = {
|
|
@@ -95978,28 +96380,28 @@ function installBundledSkills(opts) {
|
|
|
95978
96380
|
result.bundledDir = bundled;
|
|
95979
96381
|
if (!bundled) return result;
|
|
95980
96382
|
try {
|
|
95981
|
-
|
|
96383
|
+
fs65.mkdirSync(result.targetDir, { recursive: true });
|
|
95982
96384
|
} catch (e) {
|
|
95983
96385
|
result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
|
|
95984
96386
|
return result;
|
|
95985
96387
|
}
|
|
95986
|
-
const markerPath =
|
|
96388
|
+
const markerPath = path65.join(result.targetDir, ".ghl-command-skills.json");
|
|
95987
96389
|
const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
|
|
95988
96390
|
const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
|
|
95989
96391
|
for (const rel of listFiles(bundled)) {
|
|
95990
96392
|
try {
|
|
95991
|
-
const src =
|
|
95992
|
-
const dest =
|
|
96393
|
+
const src = fs65.readFileSync(path65.join(bundled, rel));
|
|
96394
|
+
const dest = path65.join(result.targetDir, rel);
|
|
95993
96395
|
const srcHash = sha2563(src);
|
|
95994
96396
|
let destBuf = null;
|
|
95995
96397
|
try {
|
|
95996
|
-
destBuf =
|
|
96398
|
+
destBuf = fs65.readFileSync(dest);
|
|
95997
96399
|
} catch {
|
|
95998
96400
|
destBuf = null;
|
|
95999
96401
|
}
|
|
96000
96402
|
if (destBuf === null) {
|
|
96001
|
-
|
|
96002
|
-
|
|
96403
|
+
fs65.mkdirSync(path65.dirname(dest), { recursive: true });
|
|
96404
|
+
fs65.writeFileSync(dest, src);
|
|
96003
96405
|
newMarker.files[rel] = srcHash;
|
|
96004
96406
|
result.installed.push(rel);
|
|
96005
96407
|
continue;
|
|
@@ -96016,7 +96418,7 @@ function installBundledSkills(opts) {
|
|
|
96016
96418
|
newMarker.packageVersion = marker.packageVersion;
|
|
96017
96419
|
result.skippedOlderPackage.push(rel);
|
|
96018
96420
|
} else if (lastWritten && destHash === lastWritten) {
|
|
96019
|
-
|
|
96421
|
+
fs65.writeFileSync(dest, src);
|
|
96020
96422
|
newMarker.files[rel] = srcHash;
|
|
96021
96423
|
result.updated.push(rel);
|
|
96022
96424
|
} else {
|
|
@@ -96027,7 +96429,7 @@ function installBundledSkills(opts) {
|
|
|
96027
96429
|
}
|
|
96028
96430
|
}
|
|
96029
96431
|
try {
|
|
96030
|
-
|
|
96432
|
+
fs65.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
|
|
96031
96433
|
} catch (e) {
|
|
96032
96434
|
result.errors.push(`marker write failed: ${String(e)}`);
|
|
96033
96435
|
}
|
|
@@ -96193,7 +96595,7 @@ dotenv2.config();
|
|
|
96193
96595
|
setPkgVersion(pkg.version);
|
|
96194
96596
|
{
|
|
96195
96597
|
const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
96196
|
-
if (configDirOverride && !
|
|
96598
|
+
if (configDirOverride && !path66.isAbsolute(configDirOverride)) {
|
|
96197
96599
|
process.stderr.write(
|
|
96198
96600
|
`[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.
|
|
96199
96601
|
`
|
|
@@ -96206,20 +96608,20 @@ process.on("unhandledRejection", (reason3) => {
|
|
|
96206
96608
|
`);
|
|
96207
96609
|
});
|
|
96208
96610
|
function hardenSecretFilePerms() {
|
|
96209
|
-
const repoDir =
|
|
96611
|
+
const repoDir = path66.resolve(__dirname, "..");
|
|
96210
96612
|
const candidates = [
|
|
96211
|
-
{ file:
|
|
96613
|
+
{ file: path66.join(repoDir, "start-mcp.sh"), mode: 448 },
|
|
96212
96614
|
// Legacy registry location (pre-migration); new location lives in app-data.
|
|
96213
|
-
{ file:
|
|
96615
|
+
{ file: path66.join(repoDir, ".ghl-tokens.json"), mode: 384 },
|
|
96214
96616
|
{ file: tokenRegistryPath(), mode: 384 }
|
|
96215
96617
|
];
|
|
96216
96618
|
for (const { file: file3, mode } of candidates) {
|
|
96217
96619
|
let current;
|
|
96218
96620
|
try {
|
|
96219
|
-
if (!
|
|
96220
|
-
current =
|
|
96621
|
+
if (!fs66.existsSync(file3)) continue;
|
|
96622
|
+
current = fs66.statSync(file3).mode & 511;
|
|
96221
96623
|
if (current !== mode) {
|
|
96222
|
-
|
|
96624
|
+
fs66.chmodSync(file3, mode);
|
|
96223
96625
|
}
|
|
96224
96626
|
} catch (error) {
|
|
96225
96627
|
const message = error instanceof Error ? error.message : String(error);
|