@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/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((resolve14) => setTimeout(resolve14, ms));
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 crypto14 = require("node:crypto");
253
+ const crypto16 = require("node:crypto");
254
254
  const raw = `${os12.hostname()}:${os12.userInfo().username}:${os12.platform()}:${os12.arch()}`;
255
- return crypto14.createHash("sha256").update(raw).digest("hex").slice(0, 16);
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 crypto14 = require("node:crypto");
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 || crypto14.randomBytes(8).toString("hex");
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 = crypto14.randomBytes(8).toString("hex");
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(path66, opts = {}) {
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(path66);
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, path66, opts = {}) {
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(path66, opts)) {
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(path66, params) {
1017
- const url = new URL(path66, GHL_BASE_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, path66, options = {}, attempt2 = 0) {
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, path66, options, attempt2), `${method} ${path66}`);
1030
+ return retryAccountRead(() => this.request(method, path67, options, attempt2), `${method} ${path67}`);
1031
1031
  }
1032
1032
  if (attempt2 === 0) {
1033
- assertWriteAllowed(method, path66, {
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(path66, options.params);
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} ${path66}`));
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} ${path66}, retry ${attempt2 + 1}/${MAX_RETRIES} in ${delay5}ms
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, path66, options, attempt2 + 1);
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} ${path66}, retry ${attempt2 + 1}/${MAX_RETRIES} in ${delay5}ms
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, path66, options, attempt2 + 1);
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} ${path66}
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(path66, options) {
1117
- return this.request("GET", path66, options);
1116
+ async get(path67, options) {
1117
+ return this.request("GET", path67, options);
1118
1118
  }
1119
- async post(path66, options) {
1120
- return this.request("POST", path66, options);
1119
+ async post(path67, options) {
1120
+ return this.request("POST", path67, options);
1121
1121
  }
1122
- async put(path66, options) {
1123
- return this.request("PUT", path66, options);
1122
+ async put(path67, options) {
1123
+ return this.request("PUT", path67, options);
1124
1124
  }
1125
- async patch(path66, options) {
1126
- return this.request("PATCH", path66, options);
1125
+ async patch(path67, options) {
1126
+ return this.request("PATCH", path67, options);
1127
1127
  }
1128
- async delete(path66, options) {
1129
- return this.request("DELETE", path66, options);
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, path66, component = "=") {
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(path66), group: groupKey(path66), component, token: token2, real });
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(), path66 = []) {
1718
- let out = url.replace(USERINFO_RE, (_m, pre, pass, at) => `${pre}${maskCredential(pass, rec2, path66, "@")}${at}`);
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, path66, `?${decoded2}#${n}`)}`;
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, path66) {
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 = [...path66, segs[i]];
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 = [...path66, prop];
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, path66) {
1785
+ function walk(node, rec2, path67) {
1786
1786
  if (Array.isArray(node)) {
1787
- node.forEach((item, i) => walk(item, rec2, [...path66, `[${i}]`]));
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, [...path66, "attributes"]);
1792
+ maskWebhookBag(node.attributes, rec2, [...path67, "attributes"]);
1793
1793
  return;
1794
1794
  }
1795
1795
  if (isWebhookSummary(node)) {
1796
- maskWebhookBag(node, rec2, path66);
1796
+ maskWebhookBag(node, rec2, path67);
1797
1797
  return;
1798
1798
  }
1799
- for (const [k, val] of Object.entries(node)) walk(val, rec2, [...path66, k]);
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, path66, fn) {
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 = [...path66, segs[i]];
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([...path66, k], v);
1970
- else if (v !== null && typeof v === "object") mapStrings(v, [...path66, k], fn);
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), [], (path66, str16) => {
1976
- out.byPath.set(pathKey(path66), str16);
1977
- const g = groupKey(path66);
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, [], (path66, str16) => {
2003
+ mapStrings(copy, [], (path67, str16) => {
2004
2004
  if (!MASK_PRESENT_RE.test(str16)) return str16;
2005
- const pk = pathKey(path66);
2006
- const gk = groupKey(path66);
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 = (path66) => JSON.stringify(path66);
2060
- groupKey = (path66) => JSON.stringify(path66.map((seg) => seg.replace(/#\d+$/, "")));
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 crypto14 = require("node:crypto");
4958
- processEphemeralId = crypto14.randomBytes(16).toString("hex");
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((resolve14, reject) => {
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
- resolve14({
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, path66, body) {
9777
- const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path66}`, body ? { body } : {});
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${path66}
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 held = obj2 ? poolNumbers(obj2) : [];
12079
- if (held.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 (held.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.`;
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 held = obj2 ? poolNumbers(obj2) : [];
12132
- for (const n of held) 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: held.length ? held : "unknown \u2014 this API does not list the pool's numbers" };
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 held = target ? poolNumbers(target) : [];
12328
+ const held2 = target ? poolNumbers(target) : [];
12329
12329
  const othersBound = others.filter((a) => boundNumbers(a).length > 0);
12330
- if (held.length) {
12330
+ if (held2.length) {
12331
12331
  for (const a of othersBound) {
12332
- const overlap = boundNumbers(a).filter((n) => held.includes(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((resolve14, reject) => {
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
- resolve14(v);
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((resolve14, reject) => {
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(() => resolve14({ status: res.statusCode ?? 0, headers: kept, body: Buffer.concat(chunks), incomplete }));
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((resolve14, reject) => {
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(() => resolve14({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString("utf8") }));
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((resolve14, reject) => {
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
- resolve14();
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 path66 = `/page/${pageId}?locationId=${client.locationId}`;
15866
- const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path66}`, { method: "GET", headers });
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 ${path66}
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(path66) {
15934
- return client.request("GET", path66);
15933
+ async function internalGet(path67) {
15934
+ return client.request("GET", path67);
15935
15935
  }
15936
- async function internalPost(path66, body) {
15937
- return client.request("POST", path66, body);
15936
+ async function internalPost(path67, body) {
15937
+ return client.request("POST", path67, body);
15938
15938
  }
15939
- async function internalPut(path66, body) {
15940
- return client.request("PUT", path66, body);
15939
+ async function internalPut(path67, body) {
15940
+ return client.request("PUT", path67, body);
15941
15941
  }
15942
- async function internalDelete(path66) {
15943
- return client.request("DELETE", path66);
15942
+ async function internalDelete(path67) {
15943
+ return client.request("DELETE", path67);
15944
15944
  }
15945
- async function funnelRequest(method, path66, body) {
15946
- const url = `https://backend.leadconnectorhq.com/funnels${path66}`;
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} ${path66}
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, path66, init) {
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) => path66.startsWith(mutating))) {
16553
- assertWriteAllowed("POST", SVC + path66, { defaultLocationId: api.locationId() });
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 + path66, { ...init, headers });
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 ${path66}${hint}
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, path66, prop) => {
16919
- if (Array.isArray(node)) return void node.forEach((x, i) => walk2(x, `${path66}[${i}]`, prop));
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, `${path66}.${k}`, WRAPPER_KEYS.has(k) ? prop : k);
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: path66, prop, kind: kindOf(prop), colour });
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 = (path66) => /^\.(sections|popups)\[\d+\]/.exec(path66)?.[0]?.slice(1) ?? "page";
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(path66, name2, taken) {
22275
+ function brandedPath(path67, name2, taken) {
22276
22276
  const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
22277
- const base = normalize2(path66 ?? "") || name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
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 [path66] = from.split(/[?#]/);
22306
- suffix = from.slice(path66.length);
22307
- matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(path66));
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, path66, body) {
22391
- const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path66}`, {
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} ${path66}
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((resolve14) => setTimeout(resolve14, attempt2 * 1e3));
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, path66, body) {
23273
- const url = `https://backend.leadconnectorhq.com/forms${path66}`;
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} ${path66}
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, path66, body) => formApiRequest(client, method, path66, body);
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 path66 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
23455
- if (formId) path66 += `&formId=${formId}`;
23456
- if (skip) path66 += `&skip=${skip}`;
23457
- const result = await formRequest("GET", path66);
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((path66, o) => client.get(path66, o), { locationId: locationId2, query: email, limit: 20 });
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, path66, body) {
24048
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path66}`;
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} ${path66}
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((resolve14) => (0, import_node_child_process.execFile)(file3, args, { timeout: 5e3 }, (error) => resolve14(!error)));
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(path66, access, item, query = {}) {
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}${path66}`);
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 === (path66.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
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((resolve14, reject) => {
26637
+ await new Promise((resolve15, reject) => {
26638
26638
  server2.once("error", reject);
26639
- server2.listen(0, "127.0.0.1", resolve14);
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, path66, init = {}) {
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}${path66}`, { method: init.method ?? "GET", headers, body, redirect: "error" });
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 (path66, query) => {
28302
- const url = new URL(`https://api.stripe.com${path66}`);
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, path66, schema, extra = {}) {
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(path66, { limit: "100", ...extra, ...after ? { starting_after: after } : {} }));
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 path66 = u.pathname.replace(/\/+$/, "");
32294
- return path66 === `${base.pathname}/event` ? "/event" : path66 === `${base.pathname}/reply` ? "/reply" : null;
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 held = [];
32394
- const clean5 = (e) => scrub(e instanceof Error ? e.message : String(e), held);
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
- held.push(c.key);
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
- held.push(c.key);
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
- held.push(writeKey);
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
- held.push(writeKey);
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 held = 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 (!held.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: held[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: held[0].holder });
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 = '${held[0].holder.replace(/'/g, "''")}';`);
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 path66 = url.pathname.replace(/\/+$/, "");
33151
- return `${host}${url.port ? `:${url.port}` : ""}${path66}`.toLowerCase();
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 path66 = 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 ${path66.join(" ").replace(/[^\p{L}\p{N}]+/gu, " ")} `;
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 (path66, params, opts) => client.get(path66, opts?.onText ? { params, onText: opts.onText } : { params });
33867
+ return (path67, params, opts) => client.get(path67, opts?.onText ? { params, onText: opts.onText } : { params });
33868
33868
  }
33869
- function settingsPathAllowed(path66, locationId2) {
33870
- return path66 === "/calendars/" || path66 === "/products/" || path66 === "/users/" || /^\/products\/[A-Za-z0-9_-]{1,64}\/price$/.test(path66) || path66 === `/locations/${encodeURIComponent(locationId2)}`;
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 (path66, params, opts) => {
33874
- if (!settingsPathAllowed(path66, locationId2)) throw new NotASettingError(`${path66} is not an account setting, so it was not read`);
33875
- return opts ? get2(path66, params, opts) : get2(path66, params);
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, path66, params, onText) => {
34079
+ const read2 = async (what, schema, path67, params, onText) => {
34080
34080
  let raw;
34081
34081
  try {
34082
- raw = await (onText ? get2(path66, params, { onText }) : get2(path66, params));
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, path66, params) => {
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(path66, params) };
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, path66, params) => {
35807
- const r = await read2(what, path66, params);
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 resolve14 = (rel) => path25.join(path25.dirname(shim), ...rel.split(/[\\/]/));
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 = resolve14(script);
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 = resolve14(program);
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((resolve14) => {
41140
+ return new Promise((resolve15) => {
41114
41141
  let settled = false;
41115
41142
  const done = (r) => {
41116
41143
  if (!settled) {
41117
41144
  settled = true;
41118
- resolve14(r);
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((resolve14) => {
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
- resolve14({ ok: false, error: reason3 });
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
- resolve14({ ok: false, error: preflight.reason });
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
- resolve14({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
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
- resolve14({ ok: false, error: reason3, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked });
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
- resolve14({ ok: false, error: inventoryError, engineVersion, engineModel, costUsd, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked });
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
- resolve14(outcome2);
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
- resolve14(outcome);
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
- resolve14(outcome);
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
- resolve14({ ok: false, error: line4, ...engineAcct });
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((resolve14) => {
42942
+ return new Promise((resolve15) => {
42915
42943
  let bin;
42916
42944
  try {
42917
42945
  bin = claudeBin();
42918
42946
  } catch {
42919
42947
  opts.onCost?.();
42920
- resolve14("");
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
- resolve14(texts.pick());
42982
+ resolve15(texts.pick());
42955
42983
  });
42956
42984
  child.on("close", () => {
42957
42985
  clearTimeout(timeout);
42958
42986
  report3();
42959
- resolve14(texts.pick());
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((resolve14) => setTimeout(resolve14, ms));
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, path66, params, key2, nameField, version) => ({ label: label2, path: path66, params, key: key2, nameField, version });
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.0",
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
- document.addEventListener("click",function(e){if(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;
46139
- var a=e.target&&e.target.closest?e.target.closest('a[target="_blank"]'):null;if(!a||!a.href)return;
46140
- /* 8a LOW-5: only web addresses are opened by hand; anything else (javascript:, data:) is left to the browser. */
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((resolve14) => {
47191
+ const p = new Promise((resolve15) => {
47165
47192
  const w = this.waiters.get(key2) ?? [];
47166
- w.push(resolve14);
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 resolve14 = (v) => {
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
- resolve14({ ok: false, error: new StaleReadError() });
47228
+ resolve15({ ok: false, error: new StaleReadError() });
47202
47229
  return;
47203
47230
  }
47204
47231
  if (kept.changed) opts.onChange?.(data);
47205
- resolve14({ ok: true, data });
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
- resolve14({ ok: false, error: e });
47236
+ resolve15({ ok: false, error: e });
47210
47237
  }
47211
- }), () => resolve14({ ok: false, error: new StaleReadError() }));
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 held = (f.notYetDue ?? []).filter((m) => m.heldByWait).length;
47811
- if (held) return { graded: false, reason: `GoHighLevel was still holding the test lead, so ${held} planned message${held === 1 ? " was" : "s were"} not judged. A grade now would look better than the truth. Run the check again.` };
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((path66, o) => client.get(path66, o), { locationId: run.locationId, query: run.leadEmail, limit: 20 });
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 held = "";
48261
+ let held2 = "";
48235
48262
  let age = 0;
48236
48263
  try {
48237
- held = import_node_fs7.default.readFileSync(lock, "utf8");
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(held).pid);
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") === held) import_node_fs7.default.unlinkSync(lock);
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 held = closed && run.steps?.read ? heldWaits(run.steps.rows) : [];
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 = held.filter((h) => h.waitUntil !== void 0 && h.waitUntil <= t + 5 * 6e4);
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 = held.filter((h) => h.waitUntil !== void 0 && h.waitUntil <= t);
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 held = f?.notYetDue.filter((m) => m.heldByWait) ?? [];
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" : held.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";
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 && held.length && !f.missing.length ? quote2("warn", `${held.length} planned message${held.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.`) : "";
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 || held.length ? "warn" : "")}
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
- ${inlineSubmit ? `<div class="cds-row"><button class="cds-btn ghost" type="submit" id="start-btn" disabled>Start the check</button></div>` : ""}
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
- var b=document.getElementById("start-btn");if(b)b.disabled=false}
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 btn=document.getElementById("start-btn");btn.disabled=true;say("Starting\u2026");
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",{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})
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
- btn.disabled=false;say(r.error||"That did not start.")})
49050
- .catch(function(){btn.disabled=false;say("That did not start. Check that ${productName()} is still running.")});
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 > maxBytes) return { problem: `The logo file ${name2} is ${Math.ceil(st.size / 1024)} KB, over the ${Math.round(maxBytes / 1024)} KB limit for a report. A smaller export of it would work.` };
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, path66, want, what, mode) => ({ nodeId: id2, path: path66, want, what, ...mode ? { mode } : {} });
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, path66) {
50330
+ function pick2(node, path67) {
50210
50331
  let cur = node;
50211
- for (const seg of path66) {
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 held = heldDraftGates(saved);
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 = held.has(record.ref) || meantLive.has(record.ref) ? "invalid" : "pending";
51843
- const heldGates = full.status === "draft" && !errors.length && !goneTags.length && !unreadable.length ? held.get(record.ref) : void 0;
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 resolve14 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
52593
- const renderRefs = (s) => resolve14(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : 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, path66, value) {
54736
+ function setPath(target, path67, value) {
54602
54737
  if (value === void 0) return;
54603
- const parts = path66.split(".");
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 held = livePending(pending, nowMs);
54897
- const waiting = check.missing.filter((m) => held[normEmail(m.email)]);
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(held[normEmail(email)]);
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, path66) {
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 === path66 && fits(r)) ?? COCKPIT_ROUTES.find((r) => r.match === "pattern" && r.pattern.test(path66) && fits(r)) ?? COCKPIT_ROUTES.find((r) => r.match === "prefix" && path66.startsWith(r.path) && path66.length > r.path.length && fits(r));
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 = (path66, scope, plans, match = "exact", pattern) => ({ method: "GET", path: path66, match, pattern, scope, plans, write: false, kind: "page" });
56578
- get = (path66, scope, plans) => ({ method: "GET", path: path66, match: "exact", scope, plans, write: false, kind: "api" });
56579
- post = (path66, scope, plans) => ({ method: "POST", path: path66, match: "exact", scope, plans, write: true, kind: "api" });
56580
- any = (path66, scope, plans, write, match = "exact", kind = "api") => ({ method: "ANY", path: path66, match, scope, plans, write, kind });
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 import_node_child_process3, import_node_crypto25, fs39, path38, import_zod79, execHelper, HelperOut, PinSchema, SIGN_TIMEOUT_MS, READ_TIMEOUT_MS, DIGEST_HEX;
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
- import_node_child_process3 = require("node:child_process");
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((resolve14) => {
56886
- (0, import_node_child_process3.execFile)(file3, args, { timeout: timeoutMs, maxBuffer: 64 * 1024, shell: false, windowsHide: true, encoding: "utf8" }, (err, stdout) => {
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 resolve14({ code: null, stdout: "", missing: true });
56889
- if (err && "killed" in err && err.killed) return resolve14({ code: null, stdout: "", timedOut: true });
56890
- resolve14({ code: err ? typeof code === "number" ? code : null : 0, stdout: typeof stdout === "string" ? stdout : "" });
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((resolve14) => {
57525
+ const late = new Promise((resolve15) => {
57391
57526
  timer = setTimeout(() => {
57392
57527
  stop3.abort(new Error("lead lookup timed out"));
57393
- resolve14(null);
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 held = await gateAgain(ghl, store2, { client, d, text: args.text, contactId, now, brief: briefNow, glossary, firstName: contact.firstName ?? null, ...live ? { live } : {} });
58823
- if (held) return held;
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 held = 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 });
58899
- if (held && !held.ok && held.sent === false) return { ok: true, found: [held.why.replace(/^Not sent: /, "")] };
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, path66) {
58906
- return path66 === "cockpit" && deps.liveSendEnabled?.() === true && deps.outOfBandReady?.() === true;
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((resolve14, reject) => {
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
- resolve14(JSON.parse(Buffer.concat(chunks).toString("utf8")));
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 path66 = u.pathname;
59959
- if (path66 !== "/brain" && path66 !== "/brain/handoff" && path66 !== MASTER_PAGE_PATH && !path66.startsWith("/api/brain/")) return false;
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" && path66 === "/brain/handoff") {
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" && path66 === "/api/brain/handoff") {
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" && path66 === "/api/brain/export") {
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" && path66 === "/api/brain/pause") {
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 (path66 === MASTER_PAGE_PATH || path66 === MASTER_API_PATH) {
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 (path66 === MASTER_PAGE_PATH) {
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" && path66 === "/brain") {
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" && path66 === BRAIN_PENDING_PATH) {
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" && path66 === BRAIN_SEND_LOG_PATH) {
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" && (path66 === "/api/brain/decide" || path66 === "/api/brain/send" || path66 === "/api/brain/send-mode" || path66 === "/api/brain/confirm-mappings")) {
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 (path66 === "/api/brain/decide") {
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 (path66 === "/api/brain/confirm-mappings") {
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 (path66 === "/api/brain/send-mode") {
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 import_node_child_process4, import_node_fs14, GHL_SERVER_NAME, CLAUDE_CODE_ADD_ARGS, CLAUDE_CODE_INSTALL_LINE, CLAUDE_CODE_ADD_HELP, OURS, defaultRunner;
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
- import_node_child_process4 = require("node:child_process");
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((resolve14) => {
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
- resolve14(r);
60983
+ resolve15(r);
60849
60984
  }
60850
60985
  };
60851
60986
  let child;
60852
60987
  try {
60853
- child = (0, import_node_child_process4.spawn)(command, args, { stdio: ["ignore", "pipe", "pipe"], env: env2 });
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 ?? import_node_child_process5.spawn;
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 import_node_child_process5, import_node_fs15, import_node_path13, import_node_crypto29, SLUG2, MODEL, JOINED_FLAG, jobs, children;
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
- import_node_child_process5 = require("node:child_process");
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((resolve14, reject) => {
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, import_node_child_process6.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "pipe"] });
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) resolve14(run.pick());
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 import_node_child_process6;
61438
+ var import_node_child_process7;
61304
61439
  var init_claude_call = __esm({
61305
61440
  "src/claude-call.ts"() {
61306
61441
  "use strict";
61307
- import_node_child_process6 = require("node:child_process");
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 = saved?.calendarTeamsBuilt;
61439
- return built && Object.keys(built).length ? { ...facts, calendarTeamsBuilt: built } : facts;
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 state2 = handoffInAccount(h, input.facts ?? {}, input.readback ?? [], input.plannedUsers ?? [], openRecipientJobs, plannedCalendars);
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(h, input.facts, plannedCalendars) : void 0;
61590
- push({ id: h.ref, text: handoffShownText(h, input.facts), timing: jobTiming(text3), selfReport: !state2.readable, key: requirementsKey([text3]), ...why ? { why } : {} });
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 held = [];
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
- held.push({ ...item, because: cite.because });
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, held] of Object.entries(result.held)) {
64645
- const checked = validatePrefillAnswers(questions, { [label2]: held.value });
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 held.value = checked[label2];
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 held = {};
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") held[k] = { value, because: cite.because, ...cite.claims ? { claims: true } : {} };
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 held));
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 made=((((state.buildViews||{})[id])||{}).built||[]).filter(x=>x.status==="created");if(!made.length)return "Built.";
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 made=((((state.buildViews||{})[id])||{}).built||[]).filter(x=>x.status==="created");
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 under 256 KB. Reports you hand clients carry it inside the page, so it prints and travels with a saved copy." })}
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("msgTop").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}
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 one Save, in the page header. */
66034
- const m={set textContent(t){document.getElementById("msgTop").textContent=t}};
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((resolve14, reject) => {
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
- resolve14(v);
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((resolve14) => {
69533
+ const expired2 = new Promise((resolve15) => {
69368
69534
  timer = setTimeout(() => {
69369
69535
  expiredAt = true;
69370
- resolve14({ ok: false, reason: `The rendered pass took longer than ${Math.round(deadlineMs / 1e3)} seconds and was stopped.` });
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(held) {
70089
- return held.map((h) => {
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 held = gated.held.find((h) => h.questionId === id2);
70371
- out.confirmationsNotUsed.push({ questionId: id2, value, why: held ? `the held answer is "${held.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." });
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, path66) {
71581
+ function getPath2(o, path67) {
71416
71582
  let cur = o;
71417
- for (const k of path66.split(".")) cur = isRec2(cur) ? cur[k] : void 0;
71583
+ for (const k of path67.split(".")) cur = isRec2(cur) ? cur[k] : void 0;
71418
71584
  return cur;
71419
71585
  }
71420
- function clearPath(o, path66) {
71421
- const keys = path66.split(".");
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 held = "";
71720
+ let held2 = "";
71555
71721
  try {
71556
- held = fs52.readFileSync(lock, "utf8");
71722
+ held2 = fs52.readFileSync(lock, "utf8");
71557
71723
  } catch {
71558
71724
  }
71559
- if (held === token2) fs52.rmSync(lock, { force: true });
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(resolve14 = resolvePlan2, opts = {}) {
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 resolve14({ ...id2 }, { persist: false }).catch(() => ({ error: "unreachable" }));
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((resolve14) => {
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", () => resolve14(over2 ? null : body));
72728
- req.on("error", () => resolve14(null));
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 held = req[GATE_BODY];
72743
- if (typeof held === "string") {
72919
+ const held2 = req[GATE_BODY];
72920
+ if (typeof held2 === "string") {
72744
72921
  setImmediate(() => {
72745
- if (held.length > max) {
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(held);
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
- function capBuildLabel(c){return CAP.key==="checkup"?"Run the checkup":CAP.key==="payments"?"Prepare the three packages in the catalog":CAP.key==="contacts"?"Find and review everyone who has not booked in 90 days":"Build this for "+c.name}
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
- if(hasBuild){bb=mk("button","cds-btn cap-build",capBuildLabel(c));if(busy)bb.disabled=true;
75205
- /* Audit 2026-10-28 #22: a read-only job never claims to create drafts. */
75206
- bb.onclick=async()=>{if(!confirm("For "+c.name+": "+words+String.fromCharCode(10)+String(CAP.before||"").split("{client}").join(c.name)+String.fromCharCode(10)+"Go ahead?"))return;bb.disabled=true;bb.textContent="Starting\u2026";
75207
- const tok=clientToken();
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 Build this above, or say the words below to Claude.":"Say the words below to Claude."));
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","What to say to Claude");doc.classList.add("cap-do");
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);doc.appendChild(q);
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
- const checks=["Website","Booking calendar","Phone number","Test conversation"];const said=l=>last&&last.readiness&&last.readiness[l];const nOk=checks.filter(said).length;
75309
- block.appendChild(mk("p","cap-ready-sum",nOk===0?"None of the "+checks.length+" checks is verified yet.":nOk===checks.length?"All "+checks.length+" checks are verified.":nOk+" of the "+checks.length+" checks are verified."));
75310
- const dl=mk("dl","cds-kv cap-ready");for(const l of checks){dl.appendChild(mk("dt","",l));const dd=mk("dd");if(said(l))dd.textContent=said(l);else dd.appendChild(chip("warn","Not verified"));dl.appendChild(dd)}block.appendChild(dl);doc.appendChild(block)}
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,unread=lines.length-done-waiting;
75329
- const rest=[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);
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
- if(canCreateSubAccounts===null)m.appendChild(agencyPlanQuestion());
75505
- else if(canCreateSubAccounts){m.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));m.appendChild(fold)}
75506
- else{m.appendChild(pasteKeyCard());const quiet=document.createElement("p");quiet.className="muted";
75507
- 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>.";m.appendChild(quiet)}}
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&&registerNotice){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 items = findArray(raw, r.key);
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
- if (activeRun2?.running) throw new Error("a run is already in progress");
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
- if (activeRun2?.running) throw new Error(RUN_IN_PROGRESS);
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((resolve14) => {
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
- resolve14({ out: buf, ok });
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 held = req[GATE_BODY];
79379
+ const held2 = req[GATE_BODY];
79115
79380
  let client = new URL(req.url || "/", "http://x").searchParams.get("locationId") ?? void 0;
79116
- if (!client && held) {
79381
+ if (!client && held2) {
79117
79382
  try {
79118
- const b = JSON.parse(held);
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((resolve14, reject) => {
79404
+ await new Promise((resolve15, reject) => {
79140
79405
  server2.once("error", reject);
79141
- server2.listen(port, "127.0.0.1", resolve14);
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((resolve14) => {
79428
+ return await new Promise((resolve15) => {
79164
79429
  const stop3 = () => {
79165
79430
  closeAllSecretChannels();
79166
- server2.close(() => resolve14(0));
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 = "No agency key on file. Run register_agency_key in GHL Command first.";
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((resolve14) => setTimeout(resolve14, ms));
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((resolve14) => setTimeout(resolve14, ms));
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, crypto13.randomUUID());
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, crypto13.randomUUID());
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, crypto13;
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
- crypto13 = __toESM(require("crypto"));
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, path66, body) {
81210
- const url = `${SMARTLIST_BASE}${path66}`;
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} ${path66}
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, path66) {
81461
- const response = await builderRequest(client, method, `${REPUTATION_BASE}${path66}`);
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} ${path66}
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(path66, method = "GET", body) {
81603
- const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path66}`, body ? { body } : {});
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} ${path66}
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((resolve14) => setTimeout(resolve14, ms));
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 resolve14 = (text3) => {
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 resolve14(obj2);
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 path66 = probe.path.replace("{loc}", loc);
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(path66, { params, noRetry: true });
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, import_node_child_process7.spawn)(cmd.command, cmd.args, {
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 import_node_child_process7, import_node_fs18, import_node_path16, DEFAULT_MODEL, FIRST2;
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
- import_node_child_process7 = require("node:child_process");
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, import_node_child_process8, LIST_TIMEOUT_MS, execText, execTextSync, WIN_FIELDS, LISTING, listingCommand, oneCommand, exeCommand, listingOf, bounded, argvOf, sepOf, foldText, CODE_SIGN_CLONE;
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
- import_node_child_process8 = require("node:child_process");
90112
+ import_node_child_process9 = require("node:child_process");
89847
90113
  LIST_TIMEOUT_MS = 1e4;
89848
- execText = (file3, args, timeoutMs) => new Promise((resolve14) => {
89849
- (0, import_node_child_process8.execFile)(file3, args, { maxBuffer: 64 << 20, windowsHide: true, timeout: Math.max(1, Math.floor(timeoutMs)), killSignal: "SIGKILL" }, (e, stdout) => {
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
- resolve14({ code, stdout: String(stdout ?? "") });
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, import_node_child_process8.execFileSync)(file3, args, { encoding: "utf8", timeout: Math.max(1, Math.floor(timeoutMs)), killSignal: "SIGKILL", maxBuffer: 64 << 20, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] })) };
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 held = false;
90257
+ let held2 = false;
89992
90258
  for (const j of liveJobs) {
89993
90259
  if (j.stuck) return "stuck";
89994
- if (j.held) held = true;
90260
+ if (j.held) held2 = true;
89995
90261
  }
89996
- return held ? "held" : "free";
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, import_node_child_process9.spawn)(process.execPath, [`--max-old-space-size=${o.maxOldSpaceMb ?? CHILD_MAX_OLD_SPACE_MB}`, o.entry ?? defaultEntry(), ...o.childArg ? [o.childArg] : []], {
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, import_node_child_process9, 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;
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
- import_node_child_process9 = require("node:child_process");
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 held = [...lastFull.keys()].filter((k) => k.startsWith(`${locationId2}|`) && k.endsWith(`|${detail.of}`));
90954
- if (held.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).` };
90955
- if (held.length === 1) full = lastFull.get(held[0]);
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 path66 = detail.section.split(".");
91224
+ const path67 = detail.section.split(".");
90959
91225
  let value = full;
90960
- for (const p of path66) value = obj(value)[p];
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((resolve14) => setTimeout(resolve14, ms))) {
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 held = opts.heldWorkflowGates?.get(wf.ref);
92975
- if (held?.length) manual.push({ ref: wf.ref, type: "workflow-publish", name: wf.name, reason: heldWorkflowReason(wf.name, held) });
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, path66, body) => {
93547
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path66}`;
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} ${path66}
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, path66, body) => {
93566
- const url = `https://backend.leadconnectorhq.com/funnels${path66}`;
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} ${path66}
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(fs63.readFileSync(path63.join(appDataDir(), "cockpit-state.json"), "utf8"));
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)) }] }) : runBuildPlan(args)
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 fs63, path63, import_zod120, buildNet, customFieldItemSchema, sleep7, NO_SELECTION_SCOPE, NOT_CONFIRMED_SCOPE, WHOLE_PRESET_NOT_KEPT, workflowInventorySchema, notConfirmedAsk, STAGE_LABELS;
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
- fs63 = __toESM(require("node:fs"));
95313
- path63 = __toESM(require("node:path"));
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((path66, params) => reader.get(path66, params ? { params } : void 0), loc, findings);
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, import_node_child_process10.spawn)(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true }).unref();
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, import_node_child_process10, import_node_path19, import_node_os3, USAGE2;
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
- import_node_child_process10 = require("node:child_process");
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 path65 = __toESM(require("path"));
95872
- var fs65 = __toESM(require("fs"));
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 fs64 = __toESM(require("node:fs"));
95920
- var path64 = __toESM(require("node:path"));
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 = path64.resolve(baseDir, "..", "skills");
96337
+ const candidate = path65.resolve(baseDir, "..", "skills");
95936
96338
  try {
95937
- return fs64.statSync(candidate).isDirectory() ? candidate : null;
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 fs64.readdirSync(dir, { withFileTypes: true })) {
95946
- const full = path64.join(dir, entry.name);
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(path64.relative(root, full));
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(fs64.readFileSync(markerPath, "utf8"));
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 ? path64.join(dir, "skills") : path64.join(os11.homedir(), ".claude", "skills");
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
- fs64.mkdirSync(result.targetDir, { recursive: true });
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 = path64.join(result.targetDir, ".ghl-command-skills.json");
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 = fs64.readFileSync(path64.join(bundled, rel));
95992
- const dest = path64.join(result.targetDir, rel);
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 = fs64.readFileSync(dest);
96398
+ destBuf = fs65.readFileSync(dest);
95997
96399
  } catch {
95998
96400
  destBuf = null;
95999
96401
  }
96000
96402
  if (destBuf === null) {
96001
- fs64.mkdirSync(path64.dirname(dest), { recursive: true });
96002
- fs64.writeFileSync(dest, src);
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
- fs64.writeFileSync(dest, src);
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
- fs64.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
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 && !path65.isAbsolute(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 = path65.resolve(__dirname, "..");
96611
+ const repoDir = path66.resolve(__dirname, "..");
96210
96612
  const candidates = [
96211
- { file: path65.join(repoDir, "start-mcp.sh"), mode: 448 },
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: path65.join(repoDir, ".ghl-tokens.json"), mode: 384 },
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 (!fs65.existsSync(file3)) continue;
96220
- current = fs65.statSync(file3).mode & 511;
96621
+ if (!fs66.existsSync(file3)) continue;
96622
+ current = fs66.statSync(file3).mode & 511;
96221
96623
  if (current !== mode) {
96222
- fs65.chmodSync(file3, mode);
96624
+ fs66.chmodSync(file3, mode);
96223
96625
  }
96224
96626
  } catch (error) {
96225
96627
  const message = error instanceof Error ? error.message : String(error);