@elitedcs/ghl-mcp 3.84.7 → 3.84.8

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
@@ -127,7 +127,7 @@ var init_account_read_retry = __esm({
127
127
  inBuildRequestScope = () => buildScope.getStore() !== void 0;
128
128
  inAccountReadAttempt = () => readScope.getStore() === true;
129
129
  withBuildRequestScope = (run, wait = sleep) => buildScope.run({ wait }, run);
130
- sleep = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
130
+ sleep = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms));
131
131
  }
132
132
  });
133
133
 
@@ -446,13 +446,13 @@ function setProtectedAccount(locationId2, name, file2 = accountRulesPath()) {
446
446
  function isWriteMethod(method) {
447
447
  return WRITE_METHODS.has(method.toUpperCase());
448
448
  }
449
- function locationIdsInRequest(path41, opts = {}) {
449
+ function locationIdsInRequest(path42, opts = {}) {
450
450
  const found = /* @__PURE__ */ new Set();
451
451
  const add2 = (v) => {
452
452
  if (typeof v === "string" && v.trim()) found.add(v.trim());
453
453
  else if (Array.isArray(v)) v.forEach(add2);
454
454
  };
455
- const seg = /\/locations\/([^/?#]+)/i.exec(path41);
455
+ const seg = /\/locations\/([^/?#]+)/i.exec(path42);
456
456
  if (seg) add2(decodeURIComponent(seg[1]));
457
457
  for (const src of [opts.params, opts.body]) {
458
458
  if (!src || typeof src !== "object") continue;
@@ -479,7 +479,7 @@ function protectedRefusalFor(locationId2, rulesFile) {
479
479
  const name = rules.protected[id];
480
480
  return name ? protectedRefusalMessage(name, false) : null;
481
481
  }
482
- function assertWriteAllowed(method, path41, opts = {}) {
482
+ function assertWriteAllowed(method, path42, opts = {}) {
483
483
  if (!isWriteMethod(method)) return;
484
484
  let rules;
485
485
  try {
@@ -488,7 +488,7 @@ function assertWriteAllowed(method, path41, opts = {}) {
488
488
  throw new Error(UNREADABLE_RULES_MSG);
489
489
  }
490
490
  if (Object.keys(rules.protected).length === 0) return;
491
- for (const id of locationIdsInRequest(path41, opts)) {
491
+ for (const id of locationIdsInRequest(path42, opts)) {
492
492
  const name = rules.protected[id];
493
493
  if (name) throw new Error(protectedRefusalMessage(name, false));
494
494
  }
@@ -646,8 +646,8 @@ var init_ghl_client = __esm({
646
646
  Version: version || GHL_API_VERSION
647
647
  };
648
648
  }
649
- buildUrl(path41, params) {
650
- const url = new URL(path41, GHL_BASE_URL);
649
+ buildUrl(path42, params) {
650
+ const url = new URL(path42, GHL_BASE_URL);
651
651
  if (params) {
652
652
  for (const [key, value] of Object.entries(params)) {
653
653
  if (value !== void 0 && value !== null) {
@@ -657,19 +657,19 @@ var init_ghl_client = __esm({
657
657
  }
658
658
  return url.toString();
659
659
  }
660
- async request(method, path41, options = {}, attempt = 0) {
660
+ async request(method, path42, options = {}, attempt = 0) {
661
661
  this.signal?.throwIfAborted();
662
662
  if (inBuildRequestScope() && method === "GET" && !inAccountReadAttempt()) {
663
- return retryAccountRead(() => this.request(method, path41, options, attempt), `${method} ${path41}`);
663
+ return retryAccountRead(() => this.request(method, path42, options, attempt), `${method} ${path42}`);
664
664
  }
665
665
  if (attempt === 0) {
666
- assertWriteAllowed(method, path41, {
666
+ assertWriteAllowed(method, path42, {
667
667
  params: options.params,
668
668
  body: options.body,
669
669
  defaultLocationId: this.defaultLocationId
670
670
  });
671
671
  }
672
- const url = this.buildUrl(path41, options.params);
672
+ const url = this.buildUrl(path42, options.params);
673
673
  const headers = this.buildHeaders(options.version);
674
674
  const fetchOptions = {
675
675
  method,
@@ -690,14 +690,14 @@ var init_ghl_client = __esm({
690
690
  clearTimeout(timeout);
691
691
  this.signal?.throwIfAborted();
692
692
  if (error instanceof Error && error.name === "AbortError") {
693
- throw requestError(new Error(`Request timeout (30s): ${method} ${path41}`));
693
+ throw requestError(new Error(`Request timeout (30s): ${method} ${path42}`));
694
694
  }
695
695
  if (!inBuildRequestScope() && !options.noRetry && attempt < MAX_RETRIES) {
696
696
  const delay5 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
697
- process.stderr.write(`[ghl-mcp] Network error on ${method} ${path41}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
697
+ process.stderr.write(`[ghl-mcp] Network error on ${method} ${path42}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
698
698
  `);
699
699
  await new Promise((r) => setTimeout(r, delay5));
700
- return this.request(method, path41, options, attempt + 1);
700
+ return this.request(method, path42, options, attempt + 1);
701
701
  }
702
702
  throw requestError(error);
703
703
  } finally {
@@ -705,10 +705,10 @@ var init_ghl_client = __esm({
705
705
  }
706
706
  if (!inBuildRequestScope() && !options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
707
707
  const delay5 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
708
- process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path41}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
708
+ process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path42}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
709
709
  `);
710
710
  await new Promise((r) => setTimeout(r, delay5));
711
- return this.request(method, path41, options, attempt + 1);
711
+ return this.request(method, path42, options, attempt + 1);
712
712
  }
713
713
  if (!response.ok) {
714
714
  let errorBody = "";
@@ -717,7 +717,7 @@ var init_ghl_client = __esm({
717
717
  } catch {
718
718
  }
719
719
  throw requestError(new Error(
720
- `GHL API Error ${response.status} ${response.statusText}: ${method} ${path41}
720
+ `GHL API Error ${response.status} ${response.statusText}: ${method} ${path42}
721
721
  ${errorBody}`
722
722
  ));
723
723
  }
@@ -738,20 +738,20 @@ ${errorBody}`
738
738
  combined?.dispose();
739
739
  }
740
740
  }
741
- async get(path41, options) {
742
- return this.request("GET", path41, options);
741
+ async get(path42, options) {
742
+ return this.request("GET", path42, options);
743
743
  }
744
- async post(path41, options) {
745
- return this.request("POST", path41, options);
744
+ async post(path42, options) {
745
+ return this.request("POST", path42, options);
746
746
  }
747
- async put(path41, options) {
748
- return this.request("PUT", path41, options);
747
+ async put(path42, options) {
748
+ return this.request("PUT", path42, options);
749
749
  }
750
- async patch(path41, options) {
751
- return this.request("PATCH", path41, options);
750
+ async patch(path42, options) {
751
+ return this.request("PATCH", path42, options);
752
752
  }
753
- async delete(path41, options) {
754
- return this.request("DELETE", path41, options);
753
+ async delete(path42, options) {
754
+ return this.request("DELETE", path42, options);
755
755
  }
756
756
  /**
757
757
  * Helper: resolves locationId from args or falls back to default
@@ -5472,12 +5472,12 @@ function registerFormTools(server2, client) {
5472
5472
  startAt: import_zod16.z.string().optional().describe("Start date filter (ISO string)"),
5473
5473
  endAt: import_zod16.z.string().optional().describe("End date filter (ISO string)")
5474
5474
  },
5475
- async ({ locationId: locationId2, page, limit, formId, q: q2, startAt, endAt }) => {
5475
+ async ({ locationId: locationId2, page, limit, formId, q: q3, startAt, endAt }) => {
5476
5476
  const resolvedLocationId = client.resolveLocationId(locationId2);
5477
5477
  const params = { locationId: resolvedLocationId, limit: limit ?? 20 };
5478
5478
  if (page !== void 0) params.page = page;
5479
5479
  if (formId !== void 0) params.formId = formId;
5480
- if (q2 !== void 0) params.q = q2;
5480
+ if (q3 !== void 0) params.q = q3;
5481
5481
  if (startAt !== void 0) params.startAt = startAt;
5482
5482
  if (endAt !== void 0) params.endAt = endAt;
5483
5483
  return client.get("/forms/submissions", { params });
@@ -5526,13 +5526,13 @@ function registerSurveyTools(server2, client) {
5526
5526
  startAt: import_zod17.z.string().optional().describe("Start date filter (ISO string)"),
5527
5527
  endAt: import_zod17.z.string().optional().describe("End date filter (ISO string)")
5528
5528
  },
5529
- async ({ locationId: locationId2, page, limit, surveyId, q: q2, startAt, endAt }) => {
5529
+ async ({ locationId: locationId2, page, limit, surveyId, q: q3, startAt, endAt }) => {
5530
5530
  const resolvedLocationId = client.resolveLocationId(locationId2);
5531
5531
  const params = { locationId: resolvedLocationId };
5532
5532
  if (page !== void 0) params.page = page;
5533
5533
  if (limit !== void 0) params.limit = limit;
5534
5534
  if (surveyId !== void 0) params.surveyId = surveyId;
5535
- if (q2 !== void 0) params.q = q2;
5535
+ if (q3 !== void 0) params.q = q3;
5536
5536
  if (startAt !== void 0) params.startAt = startAt;
5537
5537
  if (endAt !== void 0) params.endAt = endAt;
5538
5538
  return client.get("/surveys/submissions", { params });
@@ -6192,12 +6192,12 @@ function buildCreateUserBody(args, opts = {}) {
6192
6192
  function generateTemporaryPassword() {
6193
6193
  const upper2 = "ABCDEFGHJKLMNPQRSTUVWXYZ";
6194
6194
  const lower = "abcdefghijkmnopqrstuvwxyz";
6195
- const digits2 = "23456789";
6195
+ const digits = "23456789";
6196
6196
  const symbols = "!@#$%&*?";
6197
- const all = upper2 + lower + digits2 + symbols;
6197
+ const all = upper2 + lower + digits + symbols;
6198
6198
  const bytes = (0, import_node_crypto.randomBytes)(24);
6199
6199
  const pick2 = (set, b) => set[b % set.length];
6200
- const chars = [pick2(upper2, bytes[0]), pick2(lower, bytes[1]), pick2(digits2, bytes[2]), pick2(symbols, bytes[3])];
6200
+ const chars = [pick2(upper2, bytes[0]), pick2(lower, bytes[1]), pick2(digits, bytes[2]), pick2(symbols, bytes[3])];
6201
6201
  for (let i = 4; i < 20; i++) chars.push(pick2(all, bytes[i]));
6202
6202
  for (let i = chars.length - 1; i > 0; i--) {
6203
6203
  const j = bytes[(i + 20) % bytes.length] % (i + 1);
@@ -7105,11 +7105,11 @@ function registerEmailTools(server2, client) {
7105
7105
  function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
7106
7106
  if (!builderClient) return;
7107
7107
  const client = builderClient;
7108
- async function builderRequest2(method, path41, body) {
7109
- const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path41}`, body ? { body } : {});
7108
+ async function builderRequest2(method, path42, body) {
7109
+ const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path42}`, body ? { body } : {});
7110
7110
  if (!response.ok) {
7111
7111
  const text3 = await response.text();
7112
- throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path41}
7112
+ throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path42}
7113
7113
  ${text3}`);
7114
7114
  }
7115
7115
  const text2 = await response.text();
@@ -8330,18 +8330,18 @@ async function readAgent(client, agentId) {
8330
8330
  return a;
8331
8331
  }
8332
8332
  async function updateAgentFields(client, agentId, change) {
8333
- const clean = {};
8334
- for (const [k, val] of Object.entries(change)) if (val !== void 0) clean[k] = val;
8335
- if (Object.keys(clean).length === 0) throw new Error("Nothing to update \u2014 pass at least one field to change.");
8333
+ const clean2 = {};
8334
+ for (const [k, val] of Object.entries(change)) if (val !== void 0) clean2[k] = val;
8335
+ if (Object.keys(clean2).length === 0) throw new Error("Nothing to update \u2014 pass at least one field to change.");
8336
8336
  const before = await readAgent(client, agentId);
8337
- const body = { ...promptTrio(before), ...clean };
8337
+ const body = { ...promptTrio(before), ...clean2 };
8338
8338
  const merged = promptTrio(body);
8339
8339
  if (!merged.personality || !merged.goal || !merged.instructions) {
8340
8340
  throw new Error(`Agent ${agentId} read back without its personality/goal/instructions, and GoHighLevel refuses an update that does not carry all three. Pass all three explicitly (get_conversation_agent shows what is stored).`);
8341
8341
  }
8342
8342
  await client.put(`/conversation-ai/agents/${agentId}`, { version: AI_API_VERSION, body });
8343
8343
  const after = await readAgent(client, agentId);
8344
- return { before, changed: Object.keys(clean), after };
8344
+ return { before, changed: Object.keys(clean2), after };
8345
8345
  }
8346
8346
  function registerConversationAiTools(server2, client) {
8347
8347
  const v = { version: AI_API_VERSION };
@@ -8978,7 +8978,7 @@ function registerAiEmployeeDemoTools(server2, shared, deps = {}) {
8978
8978
  const utc = (s) => /^\d{4}-\d{2}-\d{2}T[\d:.]+$/.test(s) ? `${s}Z` : s;
8979
8979
  const when = (m) => Date.parse(utc(String(m.dateAdded ?? m.createdAt ?? m.dateUpdated ?? ""))) || 0;
8980
8980
  const start = Date.parse(run.startedAt) || 0;
8981
- const stop = run.endedAt ? Date.parse(run.endedAt) || Infinity : Infinity;
8981
+ const stop2 = run.endedAt ? Date.parse(run.endedAt) || Infinity : Infinity;
8982
8982
  const SKEW_MS = 15e3;
8983
8983
  let beforeStart = 0;
8984
8984
  let afterEnd = 0;
@@ -8993,7 +8993,7 @@ function registerAiEmployeeDemoTools(server2, shared, deps = {}) {
8993
8993
  beforeStart++;
8994
8994
  return false;
8995
8995
  }
8996
- if (t > stop + SKEW_MS) {
8996
+ if (t > stop2 + SKEW_MS) {
8997
8997
  afterEnd++;
8998
8998
  return false;
8999
8999
  }
@@ -10919,11 +10919,11 @@ async function readPageFull(client, pageId, fetcher = fetch) {
10919
10919
  const headers = await client.buildHeaders();
10920
10920
  headers.Origin = "https://app.gohighlevel.com";
10921
10921
  headers.Referer = "https://app.gohighlevel.com/";
10922
- const path41 = `/page/${pageId}?locationId=${client.locationId}`;
10923
- const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path41}`, { method: "GET", headers });
10922
+ const path42 = `/page/${pageId}?locationId=${client.locationId}`;
10923
+ const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path42}`, { method: "GET", headers });
10924
10924
  if (!response.ok) {
10925
10925
  const text3 = await response.text();
10926
- throw new Error(`Funnel API Error ${response.status}: GET ${path41}
10926
+ throw new Error(`Funnel API Error ${response.status}: GET ${path42}
10927
10927
  ${text3}`);
10928
10928
  }
10929
10929
  const text2 = await response.text();
@@ -10982,20 +10982,20 @@ function registerFunnelBuilderTools(server2, builderClient) {
10982
10982
  return;
10983
10983
  }
10984
10984
  const client = builderClient;
10985
- async function internalGet(path41) {
10986
- return client.request("GET", path41);
10985
+ async function internalGet(path42) {
10986
+ return client.request("GET", path42);
10987
10987
  }
10988
- async function internalPost(path41, body) {
10989
- return client.request("POST", path41, body);
10988
+ async function internalPost(path42, body) {
10989
+ return client.request("POST", path42, body);
10990
10990
  }
10991
- async function internalPut(path41, body) {
10992
- return client.request("PUT", path41, body);
10991
+ async function internalPut(path42, body) {
10992
+ return client.request("PUT", path42, body);
10993
10993
  }
10994
- async function internalDelete(path41) {
10995
- return client.request("DELETE", path41);
10994
+ async function internalDelete(path42) {
10995
+ return client.request("DELETE", path42);
10996
10996
  }
10997
- async function funnelRequest(method, path41, body) {
10998
- const url = `https://backend.leadconnectorhq.com/funnels${path41}`;
10997
+ async function funnelRequest(method, path42, body) {
10998
+ const url = `https://backend.leadconnectorhq.com/funnels${path42}`;
10999
10999
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
11000
11000
  const response = await builderRequest(client, method, url, {
11001
11001
  ...sendsBody ? { body } : {},
@@ -11008,7 +11008,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
11008
11008
  });
11009
11009
  if (!response.ok) {
11010
11010
  const text3 = await response.text();
11011
- throw new Error(`Funnel API Error ${response.status}: ${method} ${path41}
11011
+ throw new Error(`Funnel API Error ${response.status}: ${method} ${path42}
11012
11012
  ${text3}`);
11013
11013
  }
11014
11014
  const text2 = await response.text();
@@ -11599,30 +11599,30 @@ function requireCompany(api) {
11599
11599
  }
11600
11600
  return company;
11601
11601
  }
11602
- async function call(api, path41, init) {
11602
+ async function call(api, path42, init) {
11603
11603
  const headers = { ...await api.headers(), Origin: "https://app.gohighlevel.com", Referer: "https://app.gohighlevel.com/" };
11604
- if (MUTATING_PATHS.some((mutating) => path41.startsWith(mutating))) {
11605
- assertWriteAllowed("POST", SVC + path41, { defaultLocationId: api.locationId() });
11604
+ if (MUTATING_PATHS.some((mutating) => path42.startsWith(mutating))) {
11605
+ assertWriteAllowed("POST", SVC + path42, { defaultLocationId: api.locationId() });
11606
11606
  }
11607
11607
  const doFetch = api.fetch ?? fetch;
11608
- const response = await doFetch(SVC + path41, { ...init, headers });
11608
+ const response = await doFetch(SVC + path42, { ...init, headers });
11609
11609
  const text2 = await response.text();
11610
11610
  if (!response.ok) {
11611
11611
  const hint = response.status === 401 && /not authorized to access this company/i.test(text2) ? " \u2014 the Firebase session is authenticated as a DIFFERENT company than the one that owns this sub-account. Run switch_location for this sub-account and try again." : response.status === 401 ? " \u2014 the template library needs the workflow-builder Firebase session, not the API key alone. Run enable_workflow_builder for this sub-account." : "";
11612
- throw new Error(`GoHighLevel template library ${response.status} on ${path41}${hint}
11612
+ throw new Error(`GoHighLevel template library ${response.status} on ${path42}${hint}
11613
11613
  ${text2.slice(0, 300)}`);
11614
11614
  }
11615
11615
  return text2 ? JSON.parse(text2) : {};
11616
11616
  }
11617
- async function searchTemplates(api, q2 = {}) {
11618
- if (q2.categories?.length) {
11619
- const wanted = new Set(q2.categories.map((name) => name.trim().toLowerCase()));
11620
- const limit = Math.min(Math.max(q2.limit ?? 24, 1), 200);
11621
- const offset = Math.max(q2.offset ?? 0, 0);
11617
+ async function searchTemplates(api, q3 = {}) {
11618
+ if (q3.categories?.length) {
11619
+ const wanted = new Set(q3.categories.map((name) => name.trim().toLowerCase()));
11620
+ const limit = Math.min(Math.max(q3.limit ?? 24, 1), 200);
11621
+ const offset = Math.max(q3.offset ?? 0, 0);
11622
11622
  const matches = [];
11623
11623
  const seen = /* @__PURE__ */ new Set();
11624
11624
  for (let upstreamOffset = 0; ; upstreamOffset += 200) {
11625
- const page = await searchTemplates(api, { ...q2, categories: [], limit: 200, offset: upstreamOffset });
11625
+ const page = await searchTemplates(api, { ...q3, categories: [], limit: 200, offset: upstreamOffset });
11626
11626
  const fresh = page.filter((row) => !seen.has(row._id));
11627
11627
  if (page.length && !fresh.length) throw new Error("GoHighLevel repeated a template page. The category search could not finish.");
11628
11628
  for (const row of fresh) {
@@ -11637,19 +11637,19 @@ async function searchTemplates(api, q2 = {}) {
11637
11637
  const body = {
11638
11638
  companyId: requireCompany(api),
11639
11639
  locationId: api.locationId(),
11640
- product: q2.product ?? "funnels",
11641
- keyword: q2.keyword ?? "",
11640
+ product: q3.product ?? "funnels",
11641
+ keyword: q3.keyword ?? "",
11642
11642
  type: "all",
11643
- categories: q2.categories ?? [],
11644
- features: q2.features ?? [],
11645
- tags: q2.tags ?? [],
11646
- colors: q2.colors ?? [],
11643
+ categories: q3.categories ?? [],
11644
+ features: q3.features ?? [],
11645
+ tags: q3.tags ?? [],
11646
+ colors: q3.colors ?? [],
11647
11647
  status: [],
11648
11648
  createdBy: [],
11649
11649
  isAdminView: false,
11650
11650
  system: false,
11651
- limit: Math.min(Math.max(q2.limit ?? 24, 1), 200),
11652
- offset: q2.offset ?? 0
11651
+ limit: Math.min(Math.max(q3.limit ?? 24, 1), 200),
11652
+ offset: q3.offset ?? 0
11653
11653
  };
11654
11654
  const out = await call(api, "/templates/list", { method: "POST", body: JSON.stringify(body) });
11655
11655
  return Array.isArray(out) ? out : out.templates ?? [];
@@ -11966,12 +11966,12 @@ function normaliseColour(value) {
11966
11966
  function renderedPalette(envelope) {
11967
11967
  const defs = pageVariables(envelope);
11968
11968
  const paints = [];
11969
- const walk = (node, path41, prop) => {
11970
- if (Array.isArray(node)) return void node.forEach((x, i) => walk(x, `${path41}[${i}]`, prop));
11969
+ const walk = (node, path42, prop) => {
11970
+ if (Array.isArray(node)) return void node.forEach((x, i) => walk(x, `${path42}[${i}]`, prop));
11971
11971
  if (isRecord8(node)) {
11972
11972
  for (const [k, x] of Object.entries(node)) {
11973
11973
  if (DEFINITION_KEYS.has(k) || k.startsWith("--")) continue;
11974
- walk(x, `${path41}.${k}`, WRAPPER_KEYS.has(k) ? prop : k);
11974
+ walk(x, `${path42}.${k}`, WRAPPER_KEYS.has(k) ? prop : k);
11975
11975
  }
11976
11976
  return;
11977
11977
  }
@@ -11981,7 +11981,7 @@ function renderedPalette(envelope) {
11981
11981
  const resolved2 = resolveValue(raw, defs);
11982
11982
  const colour = normaliseColour(resolved2);
11983
11983
  if (colour === null && resolved2 !== null) return;
11984
- paints.push({ path: path41, prop, kind: kindOf(prop), colour });
11984
+ paints.push({ path: path42, prop, kind: kindOf(prop), colour });
11985
11985
  };
11986
11986
  walk(envelope, "", "");
11987
11987
  const face = (name, settingsKey) => {
@@ -12020,8 +12020,8 @@ function repaintReport(before, after) {
12020
12020
  const coloured = (c) => c.startsWith("#") && hexToOklch(c).c >= CARRIES_COLOUR;
12021
12021
  for (const p of a.paints) {
12022
12022
  if (!p.colour || p.colour === "transparent") continue;
12023
- const q2 = afterByPath.get(p.path);
12024
- const to = q2?.colour ?? null;
12023
+ const q3 = afterByPath.get(p.path);
12024
+ const to = q3?.colour ?? null;
12025
12025
  const differs = to !== null && looksDifferent(p.colour, to);
12026
12026
  if (p.kind === "background" || p.kind === "text") {
12027
12027
  const t = p.kind === "text" ? tally.text : coloured(p.colour) ? tally.background : tally.neutral;
@@ -12076,7 +12076,7 @@ var init_rendered_palette = __esm({
12076
12076
  VISIBLE_CHANGE = 0.05;
12077
12077
  BARELY_SHARE = 0.5;
12078
12078
  CARRIES_COLOUR = 0.02;
12079
- paintGroup = (path41) => /^\.(sections|popups)\[\d+\]/.exec(path41)?.[0]?.slice(1) ?? "page";
12079
+ paintGroup = (path42) => /^\.(sections|popups)\[\d+\]/.exec(path42)?.[0]?.slice(1) ?? "page";
12080
12080
  looksDifferent = (a, b) => {
12081
12081
  if (a === b) return false;
12082
12082
  if (a === "transparent" || b === "transparent" || a.startsWith("rgba") || b.startsWith("rgba")) return a !== b;
@@ -15700,12 +15700,12 @@ function deriveBrandDna(raw, opts = {}) {
15700
15700
  fixed = true;
15701
15701
  }
15702
15702
  const vb = svg.match(/viewBox=["']\s*[\d.-]+\s+[\d.-]+\s+([\d.]+)\s+([\d.]+)/i);
15703
- const clean = sanitizeSvgOrDrop(svg);
15704
- if (clean) {
15705
- logoSvg = clean;
15706
- logo = { inlineSvg: clean, whiteFillFixed: fixed, source: bl.where, rights: opts.rights ? "declared" : "inherits", ...vb && { width: parseFloat(vb[1]), height: parseFloat(vb[2]) } };
15703
+ const clean2 = sanitizeSvgOrDrop(svg);
15704
+ if (clean2) {
15705
+ logoSvg = clean2;
15706
+ logo = { inlineSvg: clean2, whiteFillFixed: fixed, source: bl.where, rights: opts.rights ? "declared" : "inherits", ...vb && { width: parseFloat(vb[1]), height: parseFloat(vb[2]) } };
15707
15707
  declared++;
15708
- evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${clean !== svg ? "; scripts, handlers and outside references stripped" : ""}`;
15708
+ evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${clean2 !== svg ? "; scripts, handlers and outside references stripped" : ""}`;
15709
15709
  } else {
15710
15710
  evidence["logo"] = `${bl.where}: not a plain drawing (or over 60 KB), so it was not kept`;
15711
15711
  }
@@ -16651,10 +16651,10 @@ function removalHoldBack(opts) {
16651
16651
  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`;
16652
16652
  return null;
16653
16653
  }
16654
- function brandedPath(path41, name, taken) {
16655
- const normalize = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
16656
- const base = normalize(path41 ?? "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
16657
- const used = new Set([...taken].map(normalize));
16654
+ function brandedPath(path42, name, taken) {
16655
+ const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
16656
+ const base = normalize2(path42 ?? "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
16657
+ const used = new Set([...taken].map(normalize2));
16658
16658
  let candidate = `${base}-branded`;
16659
16659
  for (let n = 2; used.has(candidate); n++) candidate = `${base}-branded-${n}`;
16660
16660
  taken.add(`/${candidate}`);
@@ -16663,7 +16663,7 @@ function brandedPath(path41, name, taken) {
16663
16663
  function rewriteTemplateLinks(envelope, destinations) {
16664
16664
  const links = [];
16665
16665
  const unmapped = [];
16666
- const normalize = (s) => s.replace(/^\/+|\/+$/g, "");
16666
+ const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "");
16667
16667
  function rewrite(from, kind) {
16668
16668
  if (!from) return from;
16669
16669
  let matches = [];
@@ -16681,9 +16681,9 @@ function rewriteTemplateLinks(envelope, destinations) {
16681
16681
  representation = "page";
16682
16682
  matches = destinations.filter((d2) => d2.pageId === preview[2]);
16683
16683
  } else if (!/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(from) && !from.startsWith("#")) {
16684
- const [path41] = from.split(/[?#]/);
16685
- suffix = from.slice(path41.length);
16686
- matches = destinations.filter((d2) => d2.path && normalize(d2.path) === normalize(path41));
16684
+ const [path42] = from.split(/[?#]/);
16685
+ suffix = from.slice(path42.length);
16686
+ matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(path42));
16687
16687
  }
16688
16688
  }
16689
16689
  if (matches.length !== 1) {
@@ -16695,7 +16695,7 @@ function rewriteTemplateLinks(envelope, destinations) {
16695
16695
  unmapped.push({ from, reason: "the target step was not rebuilt" });
16696
16696
  return from;
16697
16697
  }
16698
- const to = representation === "step" ? d.newStepId : representation === "page" ? `${previewPrefix}${d.newPageId}${suffix}` : `${from.startsWith("/") ? d.newPath : normalize(d.newPath)}${suffix}`;
16698
+ const to = representation === "step" ? d.newStepId : representation === "page" ? `${previewPrefix}${d.newPageId}${suffix}` : `${from.startsWith("/") ? d.newPath : normalize2(d.newPath)}${suffix}`;
16699
16699
  links.push({ from, to });
16700
16700
  return to;
16701
16701
  }
@@ -16766,8 +16766,8 @@ function registerPageStudioTools(server2, builderClient) {
16766
16766
  }
16767
16767
  return { map: out, truncated };
16768
16768
  }
16769
- async function funnelRequest(method, path41, body) {
16770
- const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path41}`, {
16769
+ async function funnelRequest(method, path42, body) {
16770
+ const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path42}`, {
16771
16771
  ...body ? { body } : {},
16772
16772
  headers: {
16773
16773
  // Internal-API calls require the app origin (autosave needs the
@@ -16776,7 +16776,7 @@ function registerPageStudioTools(server2, builderClient) {
16776
16776
  Referer: "https://app.gohighlevel.com/"
16777
16777
  }
16778
16778
  });
16779
- if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path41}
16779
+ if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path42}
16780
16780
  ${await response.text()}`);
16781
16781
  const text2 = await response.text();
16782
16782
  return text2 ? JSON.parse(text2) : {};
@@ -17283,7 +17283,7 @@ ${await response.text()}`);
17283
17283
  }
17284
17284
  const confirmed = /* @__PURE__ */ new Set();
17285
17285
  for (let attempt = 0; accepted.length && attempt < 5; attempt++) {
17286
- if (attempt) await new Promise((resolve12) => setTimeout(resolve12, attempt * 1e3));
17286
+ if (attempt) await new Promise((resolve13) => setTimeout(resolve13, attempt * 1e3));
17287
17287
  try {
17288
17288
  const listed = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}`);
17289
17289
  const current = listed.funnels?.find((item) => item._id === funnel._id);
@@ -17645,13 +17645,13 @@ function cleanFormDataForSave(formData) {
17645
17645
  const cleaned = dropRedundantCaseCollidingKeys(formData);
17646
17646
  return isJsonObject(cleaned) ? cleaned : formData;
17647
17647
  }
17648
- async function formApiRequest(client, method, path41, body) {
17649
- const url = `https://backend.leadconnectorhq.com/forms${path41}`;
17648
+ async function formApiRequest(client, method, path42, body) {
17649
+ const url = `https://backend.leadconnectorhq.com/forms${path42}`;
17650
17650
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
17651
17651
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
17652
17652
  if (!response.ok) {
17653
17653
  const text3 = await response.text();
17654
- throw new Error(`Form API Error ${response.status}: ${method} ${path41}
17654
+ throw new Error(`Form API Error ${response.status}: ${method} ${path42}
17655
17655
  ${text3}`);
17656
17656
  }
17657
17657
  const text2 = await response.text();
@@ -17693,7 +17693,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17693
17693
  );
17694
17694
  return;
17695
17695
  }
17696
- const formRequest = (method, path41, body) => formApiRequest(client, method, path41, body);
17696
+ const formRequest = (method, path42, body) => formApiRequest(client, method, path42, body);
17697
17697
  server2.tool(
17698
17698
  "get_form_full",
17699
17699
  "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.",
@@ -17827,10 +17827,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17827
17827
  },
17828
17828
  async ({ formId, limit, skip }) => {
17829
17829
  try {
17830
- let path41 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
17831
- if (formId) path41 += `&formId=${formId}`;
17832
- if (skip) path41 += `&skip=${skip}`;
17833
- const result = await formRequest("GET", path41);
17830
+ let path42 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
17831
+ if (formId) path42 += `&formId=${formId}`;
17832
+ if (skip) path42 += `&skip=${skip}`;
17833
+ const result = await formRequest("GET", path42);
17834
17834
  return {
17835
17835
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
17836
17836
  };
@@ -18074,7 +18074,7 @@ function registerFunnelQaTools(server2, client, builderClient) {
18074
18074
  last_name: contact.lastName,
18075
18075
  name: contact.name
18076
18076
  };
18077
- const same = (key, a, b) => key === "phone" ? (a ?? "").replace(/\D/g, "") === (b ?? "").replace(/\D/g, "") : (a ?? "").trim().toLowerCase() === (b ?? "").trim().toLowerCase();
18077
+ const same2 = (key, a, b) => key === "phone" ? (a ?? "").replace(/\D/g, "") === (b ?? "").replace(/\D/g, "") : (a ?? "").trim().toLowerCase() === (b ?? "").trim().toLowerCase();
18078
18078
  for (const k of args.expectStandard ?? []) {
18079
18079
  const key = k.toLowerCase();
18080
18080
  if (!(key in got)) {
@@ -18087,7 +18087,7 @@ function registerFunnelQaTools(server2, client, builderClient) {
18087
18087
  continue;
18088
18088
  }
18089
18089
  if (!got[key]) probs.push(`standard ${k}: not saved`);
18090
- else if (!same(key, got[key], exp)) probs.push(`standard ${k}: saved "${got[key]}" \u2260 sent "${exp}"`);
18090
+ else if (!same2(key, got[key], exp)) probs.push(`standard ${k}: saved "${got[key]}" \u2260 sent "${exp}"`);
18091
18091
  }
18092
18092
  const okCount = (args.expectCustom?.length ?? 0) + (args.expectStandard?.length ?? 0) - probs.length - notes.length;
18093
18093
  checks.push(probs.length ? { id: "field_fidelity", label: "Field-value persistence", status: "fail", detail: [probs.join("; "), notes.join("; ")].filter(Boolean).join(" | ") } : { id: "field_fidelity", label: "Field-value persistence", status: "pass", detail: `${okCount} asserted field value(s) persisted with the exact submitted value.${notes.length ? " " + notes.join("; ") : ""}` });
@@ -18238,13 +18238,13 @@ var init_funnel_qa = __esm({
18238
18238
  function registerPipelineBuilderTools(server2, builderClient) {
18239
18239
  if (!builderClient) return;
18240
18240
  const client = builderClient;
18241
- async function pipelineRequest(method, path41, body) {
18242
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path41}`;
18241
+ async function pipelineRequest(method, path42, body) {
18242
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path42}`;
18243
18243
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT" || method === "PATCH");
18244
18244
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
18245
18245
  if (!response.ok) {
18246
18246
  const text3 = await response.text();
18247
- throw new Error(`Pipeline API Error ${response.status}: ${method} ${path41}
18247
+ throw new Error(`Pipeline API Error ${response.status}: ${method} ${path42}
18248
18248
  ${text3}`);
18249
18249
  }
18250
18250
  const text2 = await response.text();
@@ -18548,6 +18548,22 @@ var init_features = __esm({
18548
18548
  });
18549
18549
 
18550
18550
  // src/agency-profile.ts
18551
+ var agency_profile_exports = {};
18552
+ __export(agency_profile_exports, {
18553
+ AGENCY_PROFILE_ENV: () => AGENCY_PROFILE_ENV,
18554
+ agencyProfilePath: () => agencyProfilePath,
18555
+ demoRailProfile: () => demoRailProfile,
18556
+ mergeAgencyProfile: () => mergeAgencyProfile,
18557
+ normalizeAccent: () => normalizeAccent,
18558
+ profileIsUsable: () => profileIsUsable,
18559
+ readAgencyProfile: () => readAgencyProfile,
18560
+ safeDir: () => safeDir,
18561
+ safeUrl: () => safeUrl2,
18562
+ sanitizeAgencyOs: () => sanitizeAgencyOs,
18563
+ sanitizeProfile: () => sanitizeProfile,
18564
+ saveAgencyProfileAnswer: () => saveAgencyProfileAnswer,
18565
+ writeAgencyProfile: () => writeAgencyProfile
18566
+ });
18551
18567
  function agencyProfilePath() {
18552
18568
  const override = process.env[AGENCY_PROFILE_ENV];
18553
18569
  return override && override.trim() ? override : path8.join(appDataDir(), "agency-profile.json");
@@ -18572,6 +18588,11 @@ function safeUrl2(x, max = MAX_URL) {
18572
18588
  if (u.protocol === "http:" && /^(localhost|127\.0\.0\.1|\[::1\])$/.test(u.hostname)) return raw;
18573
18589
  return void 0;
18574
18590
  }
18591
+ function safeDir(x) {
18592
+ const raw = typeof x === "string" ? x.trim() : "";
18593
+ if (!raw || raw.length > 1024 || raw.includes("\0") || !path8.isAbsolute(raw)) return void 0;
18594
+ return path8.normalize(raw);
18595
+ }
18575
18596
  function normalizeAccent(x) {
18576
18597
  const s = typeof x === "string" ? x.trim() : "";
18577
18598
  return /^#[0-9a-fA-F]{6}$/.test(s) ? s.toLowerCase() : void 0;
@@ -18646,6 +18667,7 @@ function sanitizeProfile(raw) {
18646
18667
  industry: str2(defaults?.industry, 64)
18647
18668
  }),
18648
18669
  agencyOs: sanitizeAgencyOs(r.agencyOs),
18670
+ clientFilesDir: safeDir(r.clientFilesDir),
18649
18671
  updated_at: str2(r.updated_at, 40)
18650
18672
  };
18651
18673
  }
@@ -18790,6 +18812,17 @@ function writeAgencyProfile(patch, file2 = agencyProfilePath()) {
18790
18812
  function profileIsUsable(p) {
18791
18813
  return Boolean(p.agencyName);
18792
18814
  }
18815
+ function demoRailProfile(p = readAgencyProfile(), ghl, demoDays = 7) {
18816
+ if (!profileIsUsable(p)) return void 0;
18817
+ const rec = { agencyName: p.agencyName };
18818
+ if (p.accent) rec.accent = p.accent;
18819
+ if (p.logoUrl) rec.logoUrl = p.logoUrl;
18820
+ if (p.sender) rec.senderIdentity = p.sender;
18821
+ const g = drop({ locationId: str2(ghl?.locationId), triggerTag: str2(ghl?.triggerTag, 60) });
18822
+ if (g) rec.ghl = g;
18823
+ rec.demoDays = num(demoDays) ?? 7;
18824
+ return rec;
18825
+ }
18793
18826
  function sanitizePlanEvidence(raw) {
18794
18827
  if (!raw || typeof raw !== "object") return void 0;
18795
18828
  const r = raw;
@@ -18839,7 +18872,8 @@ var init_agency_profile = __esm({
18839
18872
  "defaults",
18840
18873
  "agencyOs",
18841
18874
  "canCreateSubAccounts",
18842
- "ghlPlanEvidence"
18875
+ "ghlPlanEvidence",
18876
+ "clientFilesDir"
18843
18877
  ];
18844
18878
  NESTED = /* @__PURE__ */ new Set(["sender", "pricing", "delivery", "defaults", "agencyOs"]);
18845
18879
  isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -20487,8 +20521,8 @@ function summarisePrice(sub) {
20487
20521
  unpriced = true;
20488
20522
  continue;
20489
20523
  }
20490
- const q2 = i.quantity;
20491
- const qty = typeof q2 === "number" && isFinite(q2) && q2 > 0 ? Math.round(q2) : 1;
20524
+ const q3 = i.quantity;
20525
+ const qty = typeof q3 === "number" && isFinite(q3) && q3 > 0 ? Math.round(q3) : 1;
20492
20526
  total += unit * qty;
20493
20527
  }
20494
20528
  if (unpriced) {
@@ -20816,7 +20850,7 @@ function calendarSetupDetail() {
20816
20850
  }
20817
20851
  function openBrowser(url) {
20818
20852
  const [file2, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
20819
- return new Promise((resolve12) => (0, import_node_child_process.execFile)(file2, args, { timeout: 5e3 }, (error) => resolve12(!error)));
20853
+ return new Promise((resolve13) => (0, import_node_child_process.execFile)(file2, args, { timeout: 5e3 }, (error) => resolve13(!error)));
20820
20854
  }
20821
20855
  function parsed(schema, value) {
20822
20856
  const result = schema.safeParse(value);
@@ -20913,16 +20947,16 @@ function createGoogleCalendar(deps = {}) {
20913
20947
  requireOnlyCalendar(result.scope);
20914
20948
  return result.access_token;
20915
20949
  }
20916
- async function pages(path41, access, item, query = {}) {
20950
+ async function pages(path42, access, item, query = {}) {
20917
20951
  const result = [];
20918
20952
  const seen = /* @__PURE__ */ new Set();
20919
20953
  let pageToken;
20920
20954
  for (let page = 0; page < 100; page++) {
20921
- const url = new URL(`${API}${path41}`);
20955
+ const url = new URL(`${API}${path42}`);
20922
20956
  for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
20923
20957
  if (pageToken) url.searchParams.set("pageToken", pageToken);
20924
20958
  const data = parsed(
20925
- import_zod52.z.object({ items: import_zod52.z.array(item).optional(), nextPageToken: import_zod52.z.string().optional(), kind: import_zod52.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path41.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
20959
+ import_zod52.z.object({ items: import_zod52.z.array(item).optional(), nextPageToken: import_zod52.z.string().optional(), kind: import_zod52.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path42.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
20926
20960
  await request(url.href, { headers: { Authorization: `Bearer ${access}` } })
20927
20961
  );
20928
20962
  result.push(...data.items ?? []);
@@ -20974,9 +21008,9 @@ function createGoogleCalendar(deps = {}) {
20974
21008
  server2.requestTimeout = 5e3;
20975
21009
  server2.headersTimeout = 5e3;
20976
21010
  try {
20977
- await new Promise((resolve12, reject) => {
21011
+ await new Promise((resolve13, reject) => {
20978
21012
  server2.once("error", reject);
20979
- server2.listen(0, "127.0.0.1", resolve12);
21013
+ server2.listen(0, "127.0.0.1", resolve13);
20980
21014
  });
20981
21015
  } catch {
20982
21016
  server2.close();
@@ -21173,8 +21207,8 @@ function redactConnectionKey(value, key) {
21173
21207
  return value;
21174
21208
  }
21175
21209
  function stripeFetcher(key, signal) {
21176
- return async (path41, query) => {
21177
- const url = new URL(`https://api.stripe.com${path41}`);
21210
+ return async (path42, query) => {
21211
+ const url = new URL(`https://api.stripe.com${path42}`);
21178
21212
  for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
21179
21213
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` }, signal });
21180
21214
  if (!res.ok) {
@@ -21263,11 +21297,11 @@ var init_handlers = __esm({
21263
21297
  });
21264
21298
 
21265
21299
  // src/command-os/connectors/stripe-read.ts
21266
- async function listAll(f, path41, schema, extra = {}) {
21300
+ async function listAll(f, path42, schema, extra = {}) {
21267
21301
  const out = [];
21268
21302
  let after;
21269
21303
  for (let page = 0; page < 20; page++) {
21270
- const result = import_zod53.z.object({ data: import_zod53.z.array(schema), has_more: import_zod53.z.boolean().optional() }).parse(await f(path41, { limit: "100", ...extra, ...after ? { starting_after: after } : {} }));
21304
+ const result = import_zod53.z.object({ data: import_zod53.z.array(schema), has_more: import_zod53.z.boolean().optional() }).parse(await f(path42, { limit: "100", ...extra, ...after ? { starting_after: after } : {} }));
21271
21305
  out.push(...result.data);
21272
21306
  if (!result.has_more) return out;
21273
21307
  const last = import_zod53.z.object({ id: import_zod53.z.string().min(1) }).parse(result.data.at(-1));
@@ -21397,8 +21431,8 @@ function sanitizeEngagements(raw) {
21397
21431
  for (const [id, rec] of Object.entries(src).slice(0, MAX_CLIENTS)) {
21398
21432
  const key = str2(id, 64);
21399
21433
  if (!key || FORBIDDEN_KEY.has(key)) continue;
21400
- const clean = sanitizeEngagement(rec);
21401
- if (clean) clients[key] = clean;
21434
+ const clean2 = sanitizeEngagement(rec);
21435
+ if (clean2) clients[key] = clean2;
21402
21436
  }
21403
21437
  }
21404
21438
  return { v: 1, clients: { ...clients }, updated_at: str2(r.updated_at, 40) };
@@ -21879,16 +21913,16 @@ function deriveFieldKey(fieldName) {
21879
21913
  function intakeFieldName(label) {
21880
21914
  return `${INTAKE_FIELD_NAME_PREFIX}${label}`;
21881
21915
  }
21882
- function expectedFieldKey(q2) {
21883
- return deriveFieldKey(intakeFieldName(q2.label));
21916
+ function expectedFieldKey(q3) {
21917
+ return deriveFieldKey(intakeFieldName(q3.label));
21884
21918
  }
21885
21919
  function intakeSectionCount(questions = INTAKE_QUESTIONS) {
21886
21920
  let count2 = 0;
21887
21921
  let last;
21888
- for (const q2 of questions) {
21889
- if (q2.section !== last) {
21922
+ for (const q3 of questions) {
21923
+ if (q3.section !== last) {
21890
21924
  count2++;
21891
- last = q2.section;
21925
+ last = q3.section;
21892
21926
  }
21893
21927
  }
21894
21928
  return count2;
@@ -24902,15 +24936,15 @@ function shimTarget(shim) {
24902
24936
  return null;
24903
24937
  }
24904
24938
  const refs = [...text2.matchAll(/"%~?dp0%?\\([^"\r\n]+)"/g)].map((m) => m[1]);
24905
- const resolve12 = (rel) => path17.join(path17.dirname(shim), ...rel.split(/[\\/]/));
24939
+ const resolve13 = (rel) => path17.join(path17.dirname(shim), ...rel.split(/[\\/]/));
24906
24940
  const script = refs.find((r) => /\.(?:js|cjs|mjs)$/i.test(r));
24907
24941
  if (script) {
24908
- const p = resolve12(script);
24942
+ const p = resolve13(script);
24909
24943
  return fs18.existsSync(p) ? { kind: "script", path: p } : null;
24910
24944
  }
24911
24945
  const program = refs.find((r) => /\.exe$/i.test(r) && !/(^|[\\/])node\.exe$/i.test(r));
24912
24946
  if (program) {
24913
- const p = resolve12(program);
24947
+ const p = resolve13(program);
24914
24948
  return fs18.existsSync(p) ? { kind: "program", path: p } : null;
24915
24949
  }
24916
24950
  return null;
@@ -25032,12 +25066,12 @@ function noteObservedEngine(observed, file2 = enginePath()) {
25032
25066
  return true;
25033
25067
  }
25034
25068
  function runSmoke(bin, timeoutMs = 9e4) {
25035
- return new Promise((resolve12) => {
25069
+ return new Promise((resolve13) => {
25036
25070
  let settled = false;
25037
25071
  const done = (r) => {
25038
25072
  if (!settled) {
25039
25073
  settled = true;
25040
- resolve12(r);
25074
+ resolve13(r);
25041
25075
  }
25042
25076
  };
25043
25077
  const cmd = claudeCommand(bin, [
@@ -25262,13 +25296,13 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
25262
25296
  const locationName = SANDBOX_ALLOWLIST[locationId2] || opts.name || locationId2;
25263
25297
  const spec = opts.spec ?? STAGE_SPECS[stage];
25264
25298
  if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
25265
- return new Promise((resolve12) => {
25299
+ return new Promise((resolve13) => {
25266
25300
  void (async () => {
25267
25301
  const bin = claudeBin();
25268
25302
  const engineCheck = await ensureEngineReady(bin, (line3) => onEvent({ kind: "status", line: line3 }));
25269
25303
  if (!engineCheck.ok) {
25270
25304
  onEvent({ kind: "error", line: engineCheck.reason });
25271
- resolve12({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
25305
+ resolve13({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
25272
25306
  return;
25273
25307
  }
25274
25308
  onEvent({ kind: "status", line: `Starting ${spec.name} for ${locationName}.` });
@@ -25360,7 +25394,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
25360
25394
  if (code !== 0 && !lastText) {
25361
25395
  const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}`, ...engine };
25362
25396
  onEvent({ kind: "error", line: outcome2.error });
25363
- resolve12(outcome2);
25397
+ resolve13(outcome2);
25364
25398
  return;
25365
25399
  }
25366
25400
  const outcome = { ...parseResultLine(lastText), ...engine };
@@ -25373,19 +25407,19 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
25373
25407
  const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
25374
25408
  const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
25375
25409
  onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
25376
- resolve12(outcome);
25410
+ resolve13(outcome);
25377
25411
  });
25378
25412
  child.on("error", (err) => {
25379
25413
  clearTimeout(timeout);
25380
25414
  if (progressPoll) clearInterval(progressPoll);
25381
25415
  const outcome = { ok: false, error: `could not start claude: ${err.message}` };
25382
25416
  onEvent({ kind: "error", line: outcome.error });
25383
- resolve12(outcome);
25417
+ resolve13(outcome);
25384
25418
  });
25385
25419
  })().catch((err) => {
25386
25420
  const line3 = err instanceof Error ? err.message : String(err);
25387
25421
  onEvent({ kind: "error", line: line3 });
25388
- resolve12({ ok: false, error: line3 });
25422
+ resolve13({ ok: false, error: line3 });
25389
25423
  });
25390
25424
  });
25391
25425
  }
@@ -26245,8 +26279,8 @@ function lintDesignContract(html2, medium) {
26245
26279
  for (let i = 0; i < sectionTags.length; i++) {
26246
26280
  const start = sectionTags[i].index;
26247
26281
  const end = i + 1 < sectionTags.length ? sectionTags[i + 1].index : html2.length;
26248
- const inside2 = opens.filter((t) => t.index > start && t.index < end);
26249
- headlinesPerSection.push(inside2.filter((t) => t.attrs["data-role"] === "headline").length);
26282
+ const inside3 = opens.filter((t) => t.index > start && t.index < end);
26283
+ headlinesPerSection.push(inside3.filter((t) => t.attrs["data-role"] === "headline").length);
26250
26284
  }
26251
26285
  for (const t of opens) {
26252
26286
  const r = t.attrs["data-role"];
@@ -26584,13 +26618,13 @@ var init_handoff_pack = __esm({
26584
26618
 
26585
26619
  // src/command-os/handoff-run.ts
26586
26620
  function askNoTools(prompt, opts = {}) {
26587
- return new Promise((resolve12) => {
26621
+ return new Promise((resolve13) => {
26588
26622
  let bin;
26589
26623
  try {
26590
26624
  bin = claudeBin();
26591
26625
  } catch {
26592
26626
  opts.onCost?.();
26593
- resolve12("");
26627
+ resolve13("");
26594
26628
  return;
26595
26629
  }
26596
26630
  const cmd = claudeCommand(bin, ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", String(opts.maxTurns ?? 3), "--allowedTools", "", "--disallowedTools", NO_TOOLS_DENY]);
@@ -26628,12 +26662,12 @@ function askNoTools(prompt, opts = {}) {
26628
26662
  child.on("error", () => {
26629
26663
  clearTimeout(timeout);
26630
26664
  report2();
26631
- resolve12(lastText);
26665
+ resolve13(lastText);
26632
26666
  });
26633
26667
  child.on("close", () => {
26634
26668
  clearTimeout(timeout);
26635
26669
  report2();
26636
- resolve12(lastText);
26670
+ resolve13(lastText);
26637
26671
  });
26638
26672
  });
26639
26673
  }
@@ -27726,8 +27760,8 @@ function dailyBrief(input) {
27726
27760
  } else if (renewals.length) {
27727
27761
  headline3 = `${renewals[0].client}'s agreement renews ${renewals[0].inWords}.`;
27728
27762
  } else if (payingButQuiet.length) {
27729
- const q2 = payingButQuiet[0];
27730
- headline3 = payingButQuiet.length === 1 ? `${q2.client} is paying you and has booked nothing in ${q2.days} days.` : `${payingButQuiet.length} clients are paying you and have booked nothing in ${q2.days} days. The largest is ${q2.client}.`;
27763
+ const q3 = payingButQuiet[0];
27764
+ headline3 = payingButQuiet.length === 1 ? `${q3.client} is paying you and has booked nothing in ${q3.days} days.` : `${payingButQuiet.length} clients are paying you and have booked nothing in ${q3.days} days. The largest is ${q3.client}.`;
27731
27765
  } else if (underFloor.length) {
27732
27766
  headline3 = `Nothing is waiting, but ${underFloor.length} client${underFloor.length === 1 ? " is" : "s are"} billed below your own floor.`;
27733
27767
  } else if (newLeads.length) {
@@ -28057,7 +28091,7 @@ function stopReasonSentence(reason3, fetched, cap) {
28057
28091
  }
28058
28092
  }
28059
28093
  function delay(ms) {
28060
- return new Promise((resolve12) => setTimeout(resolve12, ms));
28094
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
28061
28095
  }
28062
28096
  async function fetchAllContacts(client, locationId2, options = {}) {
28063
28097
  const cap = options.maxContacts ?? DEFAULT_MAX_CONTACTS;
@@ -28763,7 +28797,7 @@ var init_client_capabilities = __esm({
28763
28797
  { key: "reputation", label: "Reputation and content", blurb: "Reviews, social posts and the blog." },
28764
28798
  { key: "health", label: "Health and handoff", blurb: "Proving it works, keeping it clean, and handing it over." }
28765
28799
  ];
28766
- N = (label, path41, params, key, nameField, version) => ({ label, path: path41, params, key, nameField, version });
28800
+ N = (label, path42, params, key, nameField, version) => ({ label, path: path42, params, key, nameField, version });
28767
28801
  AI2 = "2021-04-15";
28768
28802
  CAPABILITIES = [
28769
28803
  // ── Set up ──────────────────────────────────────────────────────────────
@@ -29024,7 +29058,7 @@ var require_package = __commonJS({
29024
29058
  "package.json"(exports2, module2) {
29025
29059
  module2.exports = {
29026
29060
  name: "@elitedcs/ghl-mcp",
29027
- version: "3.84.7",
29061
+ version: "3.84.8",
29028
29062
  mcpName: "io.github.drjerryrelth/ghl-command",
29029
29063
  description: "GoHighLevel MCP Server for Claude. 250 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
29030
29064
  main: "dist/index.js",
@@ -31395,20 +31429,20 @@ function clientHeadline(items) {
31395
31429
  const live = items.filter((i) => i.state === "in-place").length;
31396
31430
  const notYet = items.filter((i) => i.state === "not-yet").length;
31397
31431
  const waiting = items.filter((i) => i.state === "waiting").length;
31398
- const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
31432
+ const plural2 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
31399
31433
  if (!items.length) return { headline: "Your account has not been checked yet", sub: "As soon as we run the first check, this page fills in on its own." };
31400
31434
  if (!notYet && !waiting) {
31401
31435
  return {
31402
31436
  headline: "Everything we set out to build is live",
31403
- sub: `All ${plural(live, "item was", "items were")} read back from your live account, not assumed. This page re-checks itself and updates here, so it is always current.`
31437
+ sub: `All ${plural2(live, "item was", "items were")} read back from your live account, not assumed. This page re-checks itself and updates here, so it is always current.`
31404
31438
  };
31405
31439
  }
31406
31440
  const parts = [];
31407
- if (live) parts.push(`${plural(live, "thing is", "things are")} live in your account`);
31408
- if (notYet) parts.push(`${plural(notYet, "is", "are")} built but not confirmed yet`);
31409
- if (waiting) parts.push(`${plural(waiting, "is", "are")} waiting on someone else`);
31441
+ if (live) parts.push(`${plural2(live, "thing is", "things are")} live in your account`);
31442
+ if (notYet) parts.push(`${plural2(notYet, "is", "are")} built but not confirmed yet`);
31443
+ if (waiting) parts.push(`${plural2(waiting, "is", "are")} waiting on someone else`);
31410
31444
  return {
31411
- headline: notYet ? `${plural(notYet, "item", "items")} still to confirm` : `${plural(waiting, "item is", "items are")} waiting on someone else`,
31445
+ headline: notYet ? `${plural2(notYet, "item", "items")} still to confirm` : `${plural2(waiting, "item is", "items are")} waiting on someone else`,
31412
31446
  sub: `${parts.join(", ").replace(/, ([^,]*)$/, " and $1")}. Everything below was read back from your live account.`
31413
31447
  };
31414
31448
  }
@@ -31912,9 +31946,9 @@ function intakeInput(answers, industry, questions) {
31912
31946
  }
31913
31947
  function shortenPlanLine(text2, limit = 360) {
31914
31948
  if (text2.length <= limit) return text2;
31915
- const tokens = text2.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
31949
+ const tokens2 = text2.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
31916
31950
  let end = "";
31917
- for (const token2 of tokens) {
31951
+ for (const token2 of tokens2) {
31918
31952
  const next = end ? `${end} ${token2}` : token2;
31919
31953
  if (next.length > limit - 1) {
31920
31954
  if (!end) return token2.length === text2.length ? text2 : `${token2}\u2026`;
@@ -31982,8 +32016,8 @@ function executablePlanLines(saved) {
31982
32016
  const refs = [...names.keys()].sort((a, b) => b.length - a.length);
31983
32017
  const refPattern = refs.length ? new RegExp(`(?<![\\w./:@])(?:${refs.map(escapeRE).join("|")})(?![\\w]|\\.[\\w])`, "g") : void 0;
31984
32018
  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;
31985
- const resolve12 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
31986
- const renderRefs = (s) => resolve12(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
32019
+ const resolve13 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
32020
+ const renderRefs = (s) => resolve13(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
31987
32021
  const mergeLabel = (field) => {
31988
32022
  const [group, ...parts] = field.trim().split(".");
31989
32023
  const key = parts.join(".");
@@ -32954,7 +32988,7 @@ var init_run_cost = __esm({
32954
32988
  // src/claude-call.ts
32955
32989
  function callClaudeRecorded(bin, argv, context, onCost = () => {
32956
32990
  }) {
32957
- return new Promise((resolve12, reject) => {
32991
+ return new Promise((resolve13, reject) => {
32958
32992
  let text2 = "";
32959
32993
  let buffer = "";
32960
32994
  let stderr = "";
@@ -32973,7 +33007,7 @@ function callClaudeRecorded(bin, argv, context, onCost = () => {
32973
33007
  onCost("The cost record could not be saved.");
32974
33008
  }
32975
33009
  onCost(`${context.kind}: ${facts.costUsd === void 0 ? "cost not reported" : formatUsd(facts.costUsd)}.`);
32976
- if (ok) resolve12(text2);
33010
+ if (ok) resolve13(text2);
32977
33011
  else reject(new Error("Check your Claude authentication in Your agency before reading this source."));
32978
33012
  };
32979
33013
  const consume = (line3) => {
@@ -33082,9 +33116,9 @@ function unknownKeys(layer, against) {
33082
33116
  return referenced.filter((k) => !have.has(k));
33083
33117
  }
33084
33118
  function installQuestionSet(opts = {}) {
33085
- return composeQuestionSet(opts).map((q2) => {
33086
- const base = INTAKE_QUESTIONS.find((b) => b.key === q2.key);
33087
- return base && base.label !== q2.label ? { ...q2, label: base.label } : q2;
33119
+ return composeQuestionSet(opts).map((q3) => {
33120
+ const base = INTAKE_QUESTIONS.find((b) => b.key === q3.key);
33121
+ return base && base.label !== q3.label ? { ...q3, label: base.label } : q3;
33088
33122
  });
33089
33123
  }
33090
33124
  function validatePacks() {
@@ -33391,7 +33425,7 @@ function parsePricePoints(text2) {
33391
33425
  const out = [];
33392
33426
  const segments = text2.split(/\r?\n|;|\.\s+/);
33393
33427
  const ENTRY = /^(.*?)\s*[-–—:|]?\s*\$?\s*([\d,]+(?:\.\d+)?)\s*(?:[-–—]\s*\$?\s*([\d,]+(?:\.\d+)?))?\s*(\/\s*[a-z]+|(?:per|an?|each)\s+[a-z]+)?\s*$/i;
33394
- const clean = (s) => s.replace(/[\s.,:;|–—-]+$/, "").trim();
33428
+ const clean2 = (s) => s.replace(/[\s.,:;|–—-]+$/, "").trim();
33395
33429
  const num3 = (s) => s.replace(/,/g, "");
33396
33430
  const recur = (s) => s ? "/" + s.replace(/^\/|^(?:per|an?|each)\b/i, "").trim().toLowerCase() : "";
33397
33431
  for (const segment of segments) {
@@ -33399,7 +33433,7 @@ function parsePricePoints(text2) {
33399
33433
  if (!t) continue;
33400
33434
  const m = t.match(ENTRY);
33401
33435
  if (m && m[2]) {
33402
- const name = clean(m[1]);
33436
+ const name = clean2(m[1]);
33403
33437
  const price = (m[3] ? `${num3(m[2])}-${num3(m[3])}` : num3(m[2])) + recur(m[4]);
33404
33438
  out.push({ name: name || t, price });
33405
33439
  } else {
@@ -33529,11 +33563,11 @@ function splitOnMarkers(line3) {
33529
33563
  }
33530
33564
  function splitOnAnd(entry) {
33531
33565
  if (countDurations(entry) < 2) return [entry];
33532
- const tokens = entry.split(/,?\s+and\s+/i);
33533
- if (tokens.length < 2) return [entry];
33566
+ const tokens2 = entry.split(/,?\s+and\s+/i);
33567
+ if (tokens2.length < 2) return [entry];
33534
33568
  const out = [];
33535
33569
  let acc = "";
33536
- for (const tok of tokens) {
33570
+ for (const tok of tokens2) {
33537
33571
  if (acc && countDurations(acc) > 0 && countDurations(tok) > 0) {
33538
33572
  out.push(acc);
33539
33573
  acc = tok;
@@ -33748,9 +33782,9 @@ function presetForBusinessType(type) {
33748
33782
  return "generic";
33749
33783
  }
33750
33784
  }
33751
- function setPath(target, path41, value) {
33785
+ function setPath(target, path42, value) {
33752
33786
  if (value === void 0) return;
33753
- const parts = path41.split(".");
33787
+ const parts = path42.split(".");
33754
33788
  let node = target;
33755
33789
  for (let i = 0; i < parts.length - 1; i++) {
33756
33790
  const k = parts[i];
@@ -33785,10 +33819,10 @@ function buildFieldMapFromFormFields(fields, questions = INTAKE_QUESTIONS) {
33785
33819
  if (fieldKey && id) byFieldKey.set(fieldKey.toLowerCase(), id);
33786
33820
  }
33787
33821
  const map = {};
33788
- for (const q2 of questions) {
33789
- if (q2.field.kind !== "custom") continue;
33790
- const id = byFieldKey.get(expectedFieldKey(q2).toLowerCase());
33791
- if (id) map[q2.key] = id;
33822
+ for (const q3 of questions) {
33823
+ if (q3.field.kind !== "custom") continue;
33824
+ const id = byFieldKey.get(expectedFieldKey(q3).toLowerCase());
33825
+ if (id) map[q3.key] = id;
33792
33826
  }
33793
33827
  return map;
33794
33828
  }
@@ -33850,11 +33884,11 @@ function normalizeSubmissionToBrief(opts) {
33850
33884
  goal: {}
33851
33885
  };
33852
33886
  const warnings = [];
33853
- const rawFor = (q2) => {
33854
- const rawKey = q2.field.kind === "standard" ? q2.field.tag : opts.fieldMap[q2.key];
33887
+ const rawFor = (q3) => {
33888
+ const rawKey = q3.field.kind === "standard" ? q3.field.tag : opts.fieldMap[q3.key];
33855
33889
  return rawKey ? opts.others[rawKey] : void 0;
33856
33890
  };
33857
- const staffQ = questions.find((q2) => q2.briefKind === "staffList" && q2.briefPath);
33891
+ const staffQ = questions.find((q3) => q3.briefKind === "staffList" && q3.briefPath);
33858
33892
  let staff;
33859
33893
  if (staffQ) {
33860
33894
  const s = firstString(rawFor(staffQ));
@@ -33865,22 +33899,22 @@ function normalizeSubmissionToBrief(opts) {
33865
33899
  }
33866
33900
  }
33867
33901
  const staffNames = (staff ?? []).map((m) => m.name);
33868
- for (const q2 of questions) {
33869
- if (!q2.briefPath || q2.briefKind === "identity") continue;
33902
+ for (const q3 of questions) {
33903
+ if (!q3.briefPath || q3.briefKind === "identity") continue;
33870
33904
  let value;
33871
- if (q2 === staffQ) {
33905
+ if (q3 === staffQ) {
33872
33906
  value = staff;
33873
- } else if (q2.briefKind === "calendarList") {
33874
- const s = firstString(rawFor(q2));
33907
+ } else if (q3.briefKind === "calendarList") {
33908
+ const s = firstString(rawFor(q3));
33875
33909
  if (s) {
33876
33910
  const r = parseCalendarLines(s, staffNames);
33877
33911
  warnings.push(...r.warnings);
33878
33912
  value = r.calendars.length ? r.calendars : void 0;
33879
33913
  }
33880
33914
  } else {
33881
- value = coerceValue(rawFor(q2), q2.briefKind, warnings);
33915
+ value = coerceValue(rawFor(q3), q3.briefKind, warnings);
33882
33916
  }
33883
- setPath(brief, q2.briefPath, value);
33917
+ setPath(brief, q3.briefPath, value);
33884
33918
  }
33885
33919
  distillAssignments(brief, staffNames, warnings);
33886
33920
  if (warnings.length) brief.warnings = warnings;
@@ -34790,7 +34824,7 @@ function compute(ans, skip, asm) {
34790
34824
  var monthly = F.reduce(function(s, f) {
34791
34825
  return s + f.monthly;
34792
34826
  }, 0);
34793
- var stop = monthly * a("recovery");
34827
+ var stop2 = monthly * a("recovery");
34794
34828
  var oneoff = null;
34795
34829
  if (core && raw("dormant_count") !== null && a("reactivation") > 0) {
34796
34830
  oneoff = raw("dormant_count") * a("reactivation") * c * v;
@@ -34823,8 +34857,8 @@ function compute(ans, skip, asm) {
34823
34857
  core,
34824
34858
  monthly,
34825
34859
  annual: monthly * 12,
34826
- stop,
34827
- annualStop: stop * 12,
34860
+ stop: stop2,
34861
+ annualStop: stop2 * 12,
34828
34862
  oneoff,
34829
34863
  hrs,
34830
34864
  hrsWk,
@@ -34926,8 +34960,8 @@ var init_engine = __esm({
34926
34960
  show_rate: "How many booked appointments actually happen"
34927
34961
  };
34928
34962
  UNIT = {};
34929
- Q.forEach(function(q2) {
34930
- UNIT[q2[0]] = q2[4];
34963
+ Q.forEach(function(q3) {
34964
+ UNIT[q3[0]] = q3[4];
34931
34965
  });
34932
34966
  }
34933
34967
  });
@@ -34946,7 +34980,7 @@ function stageFor(opts) {
34946
34980
  function tagFor(stage) {
34947
34981
  return "assessment:" + stage.toLowerCase().replace(/\s+/g, "-");
34948
34982
  }
34949
- function renderHuman(state, computed, q2, today2) {
34983
+ function renderHuman(state, computed, q3, today2) {
34950
34984
  const lines = [];
34951
34985
  lines.push(`${NOTE_TITLE} \u2014 ${state.biz || "unnamed"}${state.trade ? " \u2014 " + state.trade : ""} \u2014 ${today2}`);
34952
34986
  if (computed) {
@@ -34961,8 +34995,8 @@ function renderHuman(state, computed, q2, today2) {
34961
34995
  const byArea = /* @__PURE__ */ new Map();
34962
34996
  const ids = [.../* @__PURE__ */ new Set([...Object.keys(state.ans), ...Object.keys(state.skip)])];
34963
34997
  for (const id of ids) {
34964
- const ask = q2.ask(id);
34965
- const area = q2.area(id);
34998
+ const ask = q3.ask(id);
34999
+ const area = q3.area(id);
34966
35000
  if (!ask || !area) continue;
34967
35001
  const skipped = state.skip[id] === true;
34968
35002
  const raw = state.ans[id];
@@ -34974,7 +35008,7 @@ function renderHuman(state, computed, q2, today2) {
34974
35008
  ${value}`);
34975
35009
  byArea.set(area, bucket);
34976
35010
  }
34977
- for (const area of q2.areaOrder()) {
35011
+ for (const area of q3.areaOrder()) {
34978
35012
  const bucket = byArea.get(area);
34979
35013
  if (!bucket || bucket.length === 0) continue;
34980
35014
  lines.push("");
@@ -34986,7 +35020,7 @@ function renderHuman(state, computed, q2, today2) {
34986
35020
  lines.push("");
34987
35021
  lines.push("ASSUMPTIONS THE OPERATOR SET");
34988
35022
  for (const [k, v] of asm) {
34989
- const label = q2.assumption?.(k) ?? k.replace(/_/g, " ");
35023
+ const label = q3.assumption?.(k) ?? k.replace(/_/g, " ");
34990
35024
  lines.push(` ${label}: ${Math.round((Number(v) || 0) * 100)}%${Number(v) > 0 ? "" : " (set to zero \u2014 its finding was dropped, not estimated)"}`);
34991
35025
  }
34992
35026
  }
@@ -35007,9 +35041,23 @@ function freshSalt() {
35007
35041
  function sanitiseHuman(text2) {
35008
35042
  return text2.split(MACHINE_MARKER).join("--- (marker text removed) ---");
35009
35043
  }
35010
- function buildNote(state, computed, q2, today2) {
35011
- const payload = JSON.stringify({ v: 1, savedAt: today2, state, computed });
35012
- return `${sanitiseHuman(renderHuman(state, computed, q2, today2))}
35044
+ function describeSource(src) {
35045
+ const title2 = src.title?.trim() ? ` "${src.title.trim()}"` : "";
35046
+ const by = src.fetchedBy === "claude" ? ", fetched by Claude" : src.fetchedBy === "upload" ? ", uploaded as a file" : "";
35047
+ return `${src.recorder} recording${title2}, recorded ${src.recordedAt}${by}`;
35048
+ }
35049
+ function buildNote(state, computed, q3, today2, source, confirmedByOwner) {
35050
+ const confirmed = confirmedByOwner?.length ? [...confirmedByOwner] : void 0;
35051
+ const payload = JSON.stringify({ v: 1, savedAt: today2, state, computed, ...source ? { source } : {}, ...confirmed ? { confirmedByOwner: confirmed } : {} });
35052
+ const human = renderHuman(state, computed, q3, today2);
35053
+ const lines = [
35054
+ ...source ? [`Source: ${describeSource(source)}`] : [],
35055
+ ...confirmed ? [`Confirmed by the owner, not heard word for word: ${confirmed.map((id) => q3.ask(id) ?? id).join("; ")}`] : []
35056
+ ];
35057
+ const withSource = lines.length ? human.replace(/\n/, `
35058
+ ${lines.join("\n")}
35059
+ `) : human;
35060
+ return `${sanitiseHuman(withSource)}
35013
35061
 
35014
35062
  ${MACHINE_MARKER}
35015
35063
  ${payload}`;
@@ -35034,7 +35082,9 @@ function parseNote(body) {
35034
35082
  if (!s.ans || typeof s.ans !== "object") return null;
35035
35083
  return {
35036
35084
  state: { biz: s.biz, trade: s.trade, ans: s.ans, skip: s.skip ?? {}, asm: s.asm ?? {}, ...s.client && typeof s.client === "object" ? { client: s.client } : {} },
35037
- computed: p.computed ?? null
35085
+ computed: p.computed ?? null,
35086
+ // Which answers the owner confirmed rather than were heard (BUG-2026-09-24-04), so the next save can carry the mark.
35087
+ ...Array.isArray(p.confirmedByOwner) ? { confirmedByOwner: p.confirmedByOwner.filter((x) => typeof x === "string") } : {}
35038
35088
  };
35039
35089
  }
35040
35090
  function buildContact(state, stage, contact) {
@@ -35111,7 +35161,16 @@ function parseNotes(bodies) {
35111
35161
  ${parts.map((p) => p.text).join("")}`);
35112
35162
  }
35113
35163
  function pickLatest(notes) {
35114
- if (!Array.isArray(notes)) return null;
35164
+ for (const parsed2 of latestFirst(notes)) if (parsed2) return parsed2;
35165
+ return null;
35166
+ }
35167
+ function pickLatestStrict(notes) {
35168
+ const all = latestFirst(notes);
35169
+ if (all.length === 0) return { record: null, clean: true };
35170
+ return { record: all[0], clean: all[0] !== null };
35171
+ }
35172
+ function latestFirst(notes) {
35173
+ if (!Array.isArray(notes)) return [];
35115
35174
  const groups = /* @__PURE__ */ new Map();
35116
35175
  notes.forEach((note, i) => {
35117
35176
  const body = typeof note?.body === "string" ? note.body : null;
@@ -35129,18 +35188,21 @@ function pickLatest(notes) {
35129
35188
  groups.set(key, g);
35130
35189
  });
35131
35190
  const ordered = [...groups.values()].sort((a, b) => a.when === b.when ? 0 : a.when > b.when ? -1 : 1);
35191
+ const out = [];
35132
35192
  for (const g of ordered) {
35133
35193
  const newestPerK = /* @__PURE__ */ new Map();
35134
35194
  for (const p of g.parts) {
35135
35195
  const seen = newestPerK.get(p.k);
35136
35196
  if (!seen || p.when > seen.when) newestPerK.set(p.k, p);
35137
35197
  }
35138
- if (newestPerK.size !== g.n) continue;
35198
+ if (newestPerK.size !== g.n) {
35199
+ out.push(null);
35200
+ continue;
35201
+ }
35139
35202
  const bodies = [...newestPerK.values()].sort((a, b) => a.k - b.k).map((p) => p.body);
35140
- const parsed2 = bodies.length === 1 ? parseNote(bodies[0]) : parseNotes(bodies);
35141
- if (parsed2) return parsed2;
35203
+ out.push(bodies.length === 1 ? parseNote(bodies[0]) : parseNotes(bodies));
35142
35204
  }
35143
- return null;
35205
+ return out;
35144
35206
  }
35145
35207
  var PIPELINE_NAME, NOTE_TITLE, MACHINE_MARKER, STAGES, MARKER_LINE, NOTE_LIMIT;
35146
35208
  var init_store3 = __esm({
@@ -35216,13 +35278,13 @@ function count(value) {
35216
35278
  const n = Number(value);
35217
35279
  return Number.isSafeInteger(n) ? n : void 0;
35218
35280
  }
35219
- function matchesQuestionChoices(q2, value) {
35220
- const fixed = q2.options !== void 0 || q2.field.kind === "custom" && ["SINGLE_OPTIONS", "MULTIPLE_OPTIONS", "CHECKBOX"].includes(q2.field.dataType);
35281
+ function matchesQuestionChoices(q3, value) {
35282
+ const fixed = q3.options !== void 0 || q3.field.kind === "custom" && ["SINGLE_OPTIONS", "MULTIPLE_OPTIONS", "CHECKBOX"].includes(q3.field.dataType);
35221
35283
  if (!fixed) return true;
35222
35284
  if (Array.isArray(value)) {
35223
- return q2.field.kind === "custom" && ["MULTIPLE_OPTIONS", "CHECKBOX"].includes(q2.field.dataType) && value.every((choice) => q2.options?.includes(choice) === true);
35285
+ return q3.field.kind === "custom" && ["MULTIPLE_OPTIONS", "CHECKBOX"].includes(q3.field.dataType) && value.every((choice) => q3.options?.includes(choice) === true);
35224
35286
  }
35225
- return q2.options?.includes(value) === true;
35287
+ return q3.options?.includes(value) === true;
35226
35288
  }
35227
35289
  function mapAssessmentToIntake(s, questions, contact) {
35228
35290
  const values = {
@@ -35248,14 +35310,14 @@ function mapAssessmentToIntake(s, questions, contact) {
35248
35310
  const answers = {};
35249
35311
  const filled = [], leftForMember = [];
35250
35312
  for (const mapping of ASSESSMENT_INTAKE_MAPPING) {
35251
- const q2 = questions.find((q3) => q3.key === mapping.target);
35313
+ const q3 = questions.find((q4) => q4.key === mapping.target);
35252
35314
  const value = values[mapping.target]?.trim();
35253
- if (!q2 || !value || !matchesQuestionChoices(q2, value)) {
35254
- leftForMember.push({ key: mapping.target, question: q2?.label ?? mapping.target, reason: !q2 ? "This question is not in this intake." : !value ? "The assessment does not give this answer." : "The answer does not match one of this intake\u2019s choices." });
35315
+ if (!q3 || !value || !matchesQuestionChoices(q3, value)) {
35316
+ leftForMember.push({ key: mapping.target, question: q3?.label ?? mapping.target, reason: !q3 ? "This question is not in this intake." : !value ? "The assessment does not give this answer." : "The answer does not match one of this intake\u2019s choices." });
35255
35317
  continue;
35256
35318
  }
35257
- answers[q2.label] = q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS" ? [value] : value;
35258
- filled.push(q2.label);
35319
+ answers[q3.label] = q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS" ? [value] : value;
35320
+ filled.push(q3.label);
35259
35321
  }
35260
35322
  return { answers, filled, leftForMember };
35261
35323
  }
@@ -35273,19 +35335,19 @@ function fillFromSnapshot(state, locationId2, questions) {
35273
35335
  const result = mapAssessmentToIntake(snapshot.assessment, questions, snapshot.contact);
35274
35336
  const answers = { ...state.intakeInputs?.[locationId2]?.answers, ...state.intakeDrafts?.[locationId2]?.answers };
35275
35337
  for (const [label, value] of Object.entries(result.answers)) {
35276
- const q2 = questions.find((q3) => q3.label === label);
35277
- const previous = answers[label] ?? answers[q2.key];
35338
+ const q3 = questions.find((q4) => q4.label === label);
35339
+ const previous = answers[label] ?? answers[q3.key];
35278
35340
  let merged = value;
35279
- if ((q2.key === "anything_else" || q2.key === "price_points") && previous && !String(previous).includes(String(value))) merged = `${previous}
35341
+ if ((q3.key === "anything_else" || q3.key === "price_points") && previous && !String(previous).includes(String(value))) merged = `${previous}
35280
35342
  ${value}`;
35281
- else if ((q2.key === "anything_else" || q2.key === "price_points") && previous) merged = previous;
35282
- if (!matchesQuestionChoices(q2, merged)) {
35343
+ else if ((q3.key === "anything_else" || q3.key === "price_points") && previous) merged = previous;
35344
+ if (!matchesQuestionChoices(q3, merged)) {
35283
35345
  result.filled = result.filled.filter((question) => question !== label);
35284
- result.leftForMember.push({ key: q2.key, question: label, reason: "The combined answer does not match this intake\u2019s choices. Your saved answer was left unchanged." });
35346
+ result.leftForMember.push({ key: q3.key, question: label, reason: "The combined answer does not match this intake\u2019s choices. Your saved answer was left unchanged." });
35285
35347
  continue;
35286
35348
  }
35287
35349
  answers[label] = merged;
35288
- if (q2.key !== label) delete answers[q2.key];
35350
+ if (q3.key !== label) delete answers[q3.key];
35289
35351
  }
35290
35352
  return { state: { ...state, intakeDrafts: { ...state.intakeDrafts, [locationId2]: { answers, savedAt: (/* @__PURE__ */ new Date()).toISOString() } } }, filled: result.filled, leftForMember: result.leftForMember };
35291
35353
  }
@@ -35373,10 +35435,10 @@ var init_attachment = __esm({
35373
35435
  async function pickIntakeContact(client, locationId2, fieldIdToLabel, knownContactId, formName) {
35374
35436
  const ids = /* @__PURE__ */ new Set();
35375
35437
  if (knownContactId) ids.add(knownContactId);
35376
- for (const q2 of [...INTAKE_TAGS, ...formName ? [formName] : []]) {
35438
+ for (const q3 of [...INTAKE_TAGS, ...formName ? [formName] : []]) {
35377
35439
  try {
35378
- const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q2, limit: 20 } });
35379
- for (const c of found.contacts ?? []) if ((c.tags ?? []).some((t) => INTAKE_TAGS.includes(t)) || q2 === formName) ids.add(c.id);
35440
+ const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q3, limit: 20 } });
35441
+ for (const c of found.contacts ?? []) if ((c.tags ?? []).some((t) => INTAKE_TAGS.includes(t)) || q3 === formName) ids.add(c.id);
35380
35442
  } catch {
35381
35443
  }
35382
35444
  }
@@ -35459,17 +35521,17 @@ function buildHeaderField(title2, index) {
35459
35521
  weight: 400
35460
35522
  };
35461
35523
  }
35462
- function buildStandardFormField(q2) {
35463
- if (q2.field.kind !== "standard") throw new Error(`buildStandardFormField: ${q2.key} is not standard`);
35464
- const tag = q2.field.tag;
35524
+ function buildStandardFormField(q3) {
35525
+ if (q3.field.kind !== "standard") throw new Error(`buildStandardFormField: ${q3.key} is not standard`);
35526
+ const tag = q3.field.tag;
35465
35527
  const isEmail = tag === "email";
35466
35528
  const isPhone = tag === "phone";
35467
35529
  const base = {
35468
35530
  fieldWidthPercentage: 100,
35469
35531
  hiddenFieldQueryKey: tag,
35470
- label: q2.label,
35471
- placeholder: q2.placeholder ?? q2.label,
35472
- required: q2.required,
35532
+ label: q3.label,
35533
+ placeholder: q3.placeholder ?? q3.label,
35534
+ required: q3.required,
35473
35535
  standard: true,
35474
35536
  tag,
35475
35537
  type: isEmail ? "email" : "text",
@@ -35478,7 +35540,7 @@ function buildStandardFormField(q2) {
35478
35540
  if (isPhone) base.enableCountryPicker = false;
35479
35541
  return base;
35480
35542
  }
35481
- function buildCustomFormField(q2, record, locationId2, position2) {
35543
+ function buildCustomFormField(q3, record, locationId2, position2) {
35482
35544
  const type = formFieldType(record.dataType);
35483
35545
  const field = {
35484
35546
  Id: record.id,
@@ -35486,7 +35548,7 @@ function buildCustomFormField(q2, record, locationId2, position2) {
35486
35548
  tag: record.id,
35487
35549
  active: false,
35488
35550
  allowCustomOption: false,
35489
- customFieldLabel: q2.label,
35551
+ customFieldLabel: q3.label,
35490
35552
  dataType: record.dataType,
35491
35553
  dateAdded: record.dateAdded ?? "",
35492
35554
  description: "",
@@ -35496,19 +35558,19 @@ function buildCustomFormField(q2, record, locationId2, position2) {
35496
35558
  fieldWidthPercentage: 100,
35497
35559
  fieldsCount: 0,
35498
35560
  hiddenFieldQueryKey: slug(record.name),
35499
- label: q2.label,
35561
+ label: q3.label,
35500
35562
  locationId: locationId2,
35501
35563
  model: record.model ?? "contact",
35502
35564
  name: record.name,
35503
35565
  parentId: record.parentId ?? "",
35504
- placeholder: q2.placeholder ?? "",
35566
+ placeholder: q3.placeholder ?? "",
35505
35567
  position: position2,
35506
- required: q2.required,
35568
+ required: q3.required,
35507
35569
  showInForms: true,
35508
35570
  standard: false,
35509
35571
  type
35510
35572
  };
35511
- const options = record.picklistOptions ?? q2.options ?? void 0;
35573
+ const options = record.picklistOptions ?? q3.options ?? void 0;
35512
35574
  if (type === "single_options" || type === "multiple_options" || type === "checkbox") {
35513
35575
  field.picklistOptions = options ? [...options] : [];
35514
35576
  }
@@ -35557,17 +35619,17 @@ function buildIntakeFormData(opts) {
35557
35619
  let lastSection = "";
35558
35620
  let headerIndex = 0;
35559
35621
  let position2 = 0;
35560
- for (const q2 of questions) {
35561
- if (q2.section !== lastSection) {
35562
- fields.push(buildHeaderField(q2.sectionLabel, headerIndex++));
35563
- lastSection = q2.section;
35622
+ for (const q3 of questions) {
35623
+ if (q3.section !== lastSection) {
35624
+ fields.push(buildHeaderField(q3.sectionLabel, headerIndex++));
35625
+ lastSection = q3.section;
35564
35626
  }
35565
- if (q2.field.kind === "standard") {
35566
- fields.push(buildStandardFormField(q2));
35627
+ if (q3.field.kind === "standard") {
35628
+ fields.push(buildStandardFormField(q3));
35567
35629
  } else {
35568
- const record = opts.resolved.get(q2.key);
35630
+ const record = opts.resolved.get(q3.key);
35569
35631
  if (!record) continue;
35570
- fields.push(buildCustomFormField(q2, record, opts.locationId, position2 += 50));
35632
+ fields.push(buildCustomFormField(q3, record, opts.locationId, position2 += 50));
35571
35633
  }
35572
35634
  }
35573
35635
  fields.push(buildSubmitButton("Submit"));
@@ -35597,33 +35659,67 @@ var init_form_template = __esm({
35597
35659
  }
35598
35660
  });
35599
35661
 
35662
+ // src/command-os/assessment/words.ts
35663
+ function tokens(s) {
35664
+ return String(s ?? "").toLowerCase().replace(/[‘’ʼ]/g, "'").replace(/[–—]/g, "-").replace(/%/g, " percent ").split(/[^a-z0-9.']+/).map((w) => w.replace(/^\.+|\.+$/g, "")).filter(Boolean);
35665
+ }
35666
+ function numericValue(value) {
35667
+ const v = String(value ?? "").trim().toLowerCase().replace(/[$,\s]/g, "");
35668
+ const m = /^(-?\d+(?:\.\d+)?)(%|k|m)?$/.exec(v);
35669
+ if (!m) return null;
35670
+ const n = Number(m[1]) * (m[2] === "k" ? 1e3 : m[2] === "m" ? 1e6 : 1);
35671
+ return { n, percent: m[2] === "%" };
35672
+ }
35673
+ var init_words = __esm({
35674
+ "src/command-os/assessment/words.ts"() {
35675
+ "use strict";
35676
+ }
35677
+ });
35678
+
35600
35679
  // src/command-os/assessment/transcript.ts
35601
35680
  function loose(s) {
35602
35681
  return String(s ?? "").replace(/[‘’ʼ]/g, "'").replace(/[“”]/g, '"').replace(/[–—]/g, "-").replace(/\s+/g, " ").trim().toLowerCase();
35603
35682
  }
35604
- function digits(s) {
35605
- return String(s ?? "").replace(/[^0-9]/g, "");
35606
- }
35607
- function valueIsInQuote(value, quote) {
35608
- const v = loose(value);
35609
- const q2 = loose(quote);
35610
- if (v.length === 0) return false;
35611
- if (q2.includes(v)) return true;
35612
- const dv = digits(value);
35613
- if (dv.length > 0 && digits(quote).includes(dv)) return true;
35614
- return false;
35683
+ function valueHeard(value, quote, unit) {
35684
+ const v = String(value ?? "").trim();
35685
+ if (unit ? NUMERIC_UNITS.has(unit) : numericValue(v) !== null) return { ok: false, because: NUMBER_HELD };
35686
+ const tv = tokens(v).join(" ");
35687
+ if (tv && ` ${tokens(quote).join(" ")} `.includes(` ${tv} `)) return { ok: true };
35688
+ return { ok: false, because: `"${v}" is not in what they said, so it was worked out rather than heard. Confirm it before it counts.` };
35615
35689
  }
35616
- function citationCheck(material, value, quote) {
35690
+ function citationCheck(material, value, quote, unit) {
35617
35691
  const v = String(value ?? "").trim();
35618
- const q2 = String(quote ?? "").trim();
35692
+ const q3 = String(quote ?? "").trim();
35619
35693
  if (v === "") return { verdict: "refused", because: "no value was given." };
35620
- if (q2 === "") return { verdict: "refused", because: "no quote was given, so there is nothing tying this answer to what they said." };
35621
- if (q2.length > MAX_QUOTE) return { verdict: "refused", because: `the quote is ${q2.length} characters. A quote that long is a summary, not a citation.` };
35694
+ const said = quoteCheck(material, q3);
35695
+ if (said.verdict === "refused") return said;
35696
+ const heard = valueHeard(v, q3, unit);
35697
+ if (!heard.ok) return { verdict: "held", because: heard.because };
35698
+ return { verdict: "ok" };
35699
+ }
35700
+ function quoteCheck(material, quote, opts = {}) {
35701
+ const q3 = String(quote ?? "").trim();
35702
+ if (q3 === "") return { verdict: "refused", because: "no quote was given, so there is nothing tying this answer to what they said." };
35703
+ if (q3.length > MAX_QUOTE) return { verdict: "refused", because: `the quote is ${q3.length} characters. A quote that long is a summary, not a citation.` };
35704
+ const needle = loose(q3);
35705
+ const words = needle.split(" ").filter((w) => WORDCHAR.test(w)).length;
35706
+ if (opts.minWords && words < opts.minWords) {
35707
+ return { verdict: "refused", because: `the quote is ${words === 1 ? "one word" : `${words} words`}. It needs at least ${opts.minWords} words to show they actually said it.` };
35708
+ }
35622
35709
  const hay = loose(material);
35623
- if (hay.length === 0 || !hay.includes(loose(q2))) return { verdict: "refused", because: "that quote does not appear in the transcript." };
35624
- if (!valueIsInQuote(v, q2)) return { verdict: "held", because: `"${v}" is not in what they said, so it was worked out rather than heard. Confirm it before it counts.` };
35710
+ if (hay.length === 0 || !containsAsWords(hay, needle)) return { verdict: "refused", because: "that quote does not appear in the transcript." };
35625
35711
  return { verdict: "ok" };
35626
35712
  }
35713
+ function containsAsWords(hay, needle) {
35714
+ const checkStart = WORDCHAR.test(needle[0] ?? "");
35715
+ const checkEnd = WORDCHAR.test(needle[needle.length - 1] ?? "");
35716
+ for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + 1)) {
35717
+ const before = i > 0 ? hay[i - 1] : "";
35718
+ const after = hay[i + needle.length] ?? "";
35719
+ if ((!checkStart || !WORDCHAR.test(before)) && (!checkEnd || !WORDCHAR.test(after))) return true;
35720
+ }
35721
+ return false;
35722
+ }
35627
35723
  function fitsItsUnit(value, unit) {
35628
35724
  if (unit === void 0 || unit === "text") return true;
35629
35725
  const n = parseTyped(value, unit);
@@ -35659,7 +35755,7 @@ function gateExtraction(transcript, proposed, questions) {
35659
35755
  rejected.push({ ...item, because: "the same question was answered twice; only the first was taken." });
35660
35756
  continue;
35661
35757
  }
35662
- const cite = citationCheck(transcript, item.value, item.quote);
35758
+ const cite = citationCheck(transcript, item.value, item.quote, questions.get(item.questionId));
35663
35759
  if (cite.verdict === "refused") {
35664
35760
  rejected.push({ ...item, because: cite.because });
35665
35761
  continue;
@@ -35685,12 +35781,16 @@ function gateExtraction(transcript, proposed, questions) {
35685
35781
  function mergeAccepted(ans, result) {
35686
35782
  return { ...ans, ...result.patch };
35687
35783
  }
35688
- var MAX_QUOTE;
35784
+ var NUMERIC_UNITS, NUMBER_HELD, MAX_QUOTE, WORDCHAR;
35689
35785
  var init_transcript = __esm({
35690
35786
  "src/command-os/assessment/transcript.ts"() {
35691
35787
  "use strict";
35692
35788
  init_engine();
35789
+ init_words();
35790
+ NUMERIC_UNITS = /* @__PURE__ */ new Set(["count", "currency", "rate", "minutes", "hours"]);
35791
+ NUMBER_HELD = "a number: confirm it. Numbers always wait for the owner's OK, because no word check can tell a figure that answers the question from one said in passing.";
35693
35792
  MAX_QUOTE = 400;
35793
+ WORDCHAR = /[\p{L}\p{N}]/u;
35694
35794
  }
35695
35795
  });
35696
35796
 
@@ -35767,8 +35867,8 @@ async function readIntakeSnapshot(client, locationId2, sentMarker) {
35767
35867
  if (!submissions) {
35768
35868
  try {
35769
35869
  const seen = /* @__PURE__ */ new Map();
35770
- for (const q2 of [INTAKE_CONTACT_TAG, INTAKE_FORM_NAME2]) {
35771
- const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q2, limit: 20 } });
35870
+ for (const q3 of [INTAKE_CONTACT_TAG, INTAKE_FORM_NAME2]) {
35871
+ const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q3, limit: 20 } });
35772
35872
  for (const c of found.contacts ?? []) seen.set(c.id, c);
35773
35873
  }
35774
35874
  const isFormSubmission = (c) => c.source === INTAKE_FORM_NAME2 || (c.attributions ?? []).some((a) => a.mediumId === form.id || a.medium === "form");
@@ -35902,15 +36002,15 @@ async function runPrefill(call2, labels, material) {
35902
36002
  }
35903
36003
  function validatePrefillAnswers(questions, proposed) {
35904
36004
  const result = {};
35905
- for (const q2 of questions) {
35906
- const raw = proposed[q2.label];
36005
+ for (const q3 of questions) {
36006
+ const raw = proposed[q3.label];
35907
36007
  if (raw === void 0) continue;
35908
36008
  const values = (Array.isArray(raw) ? raw : [raw]).map((x) => x.trim()).filter(Boolean);
35909
36009
  if (!values.length) continue;
35910
- if (q2.options?.length) {
35911
- if (values.some((v) => !q2.options.includes(v)) || !q2.multi && values.length !== 1) continue;
35912
- result[q2.label] = q2.multi ? [...new Set(values)] : values[0];
35913
- } else if (values.length === 1) result[q2.label] = values[0];
36010
+ if (q3.options?.length) {
36011
+ if (values.some((v) => !q3.options.includes(v)) || !q3.multi && values.length !== 1) continue;
36012
+ result[q3.label] = q3.multi ? [...new Set(values)] : values[0];
36013
+ } else if (values.length === 1) result[q3.label] = values[0];
35914
36014
  }
35915
36015
  return result;
35916
36016
  }
@@ -39232,9 +39332,9 @@ function renderReport2(opts) {
39232
39332
  if (opts.answered && opts.answered.length) {
39233
39333
  out.push('<h2>What we started from, in your words</h2><table class="qa">');
39234
39334
  for (var a = 0; a < opts.answered.length; a++) {
39235
- var q2 = opts.answered[a];
39236
- var numeric = q2.unit !== void 0 ? q2.unit !== "text" : isFigure(q2.value);
39237
- out.push("<tr><td>" + esc7(q2.ask) + '</td><td class="' + (numeric ? "num" : "said") + '">' + (q2.unknown ? '<span class="unk">you did not know</span>' : esc7(q2.value)) + "</td></tr>");
39335
+ var q3 = opts.answered[a];
39336
+ var numeric = q3.unit !== void 0 ? q3.unit !== "text" : isFigure(q3.value);
39337
+ out.push("<tr><td>" + esc7(q3.ask) + '</td><td class="' + (numeric ? "num" : "said") + '">' + (q3.unknown ? '<span class="unk">you did not know</span>' : esc7(q3.value)) + "</td></tr>");
39238
39338
  }
39239
39339
  out.push("</table>");
39240
39340
  }
@@ -39379,6 +39479,491 @@ var init_report = __esm({
39379
39479
  }
39380
39480
  });
39381
39481
 
39482
+ // src/command-os/client-calls.ts
39483
+ var client_calls_exports = {};
39484
+ __export(client_calls_exports, {
39485
+ ANALYSIS_MIN_WORDS: () => ANALYSIS_MIN_WORDS,
39486
+ CLIENT_FILES_ROOT: () => CLIENT_FILES_ROOT,
39487
+ CONFIRM_INSTRUCTION: () => CONFIRM_INSTRUCTION,
39488
+ EXISTING_WITH_CONTACT: () => EXISTING_WITH_CONTACT,
39489
+ RECORDERS: () => RECORDERS,
39490
+ SOLUTION_KINDS: () => SOLUTION_KINDS,
39491
+ UNCLEAN_RECORD: () => UNCLEAN_RECORD,
39492
+ buildClientFile: () => buildClientFile,
39493
+ callDate: () => callDate,
39494
+ confirmList: () => confirmList,
39495
+ gateAnalysis: () => gateAnalysis,
39496
+ processClientCall: () => processClientCall,
39497
+ safeName: () => safeName,
39498
+ writeClientFileLocally: () => writeClientFileLocally
39499
+ });
39500
+ function gateAnalysis(transcript, analysis) {
39501
+ const rejected = [];
39502
+ const kept = {
39503
+ saidTheyWant: (analysis.saidTheyWant ?? []).map(clean).filter(Boolean),
39504
+ actuallyNeed: [],
39505
+ unnamedProblems: [],
39506
+ recommendedSolutions: [],
39507
+ nextCallQuestions: (analysis.nextCallQuestions ?? []).map(clean).filter(Boolean)
39508
+ };
39509
+ const check = (section2, item, quote) => {
39510
+ if (!item) {
39511
+ rejected.push({ section: section2, item, quote, why: "the point itself was empty." });
39512
+ return false;
39513
+ }
39514
+ const said = quoteCheck(transcript, quote, { minWords: ANALYSIS_MIN_WORDS });
39515
+ if (said.verdict === "refused") {
39516
+ rejected.push({ section: section2, item, quote, why: said.because });
39517
+ return false;
39518
+ }
39519
+ return true;
39520
+ };
39521
+ for (const section2 of ["actuallyNeed", "unnamedProblems"]) {
39522
+ for (const p of analysis[section2] ?? []) {
39523
+ const point = clean(p?.point), quote = String(p?.quote ?? "").trim();
39524
+ if (check(section2, point, quote)) kept[section2].push({ point, quote });
39525
+ }
39526
+ }
39527
+ for (const s of analysis.recommendedSolutions ?? []) {
39528
+ const solution = clean(s?.solution), quote = String(s?.quote ?? "").trim();
39529
+ if (!SOLUTION_KINDS.includes(s?.kind)) {
39530
+ rejected.push({ section: "recommendedSolutions", item: solution, quote, why: `"${String(s?.kind)}" is not a kind; use ${SOLUTION_KINDS.join(", ")}.` });
39531
+ continue;
39532
+ }
39533
+ if (check("recommendedSolutions", solution, quote)) kept.recommendedSolutions.push({ solution, kind: s.kind, why: clean(s?.why), quote });
39534
+ }
39535
+ return { kept, rejected };
39536
+ }
39537
+ function safeName(raw, max = 80) {
39538
+ let s = String(raw ?? "").normalize("NFC").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/[/\\:*?"<>|]/g, " ").replace(/(^|\s)\.+(?=\s|$)/g, " ").replace(/\s+/g, " ").trim().replace(/^[.\s]+|[.\s]+$/g, "");
39539
+ const points = Array.from(s);
39540
+ if (points.length > max) s = points.slice(0, max).join("").replace(/[.\s]+$/g, "");
39541
+ if (WINDOWS_RESERVED.test(s)) return void 0;
39542
+ return s || void 0;
39543
+ }
39544
+ function callDate(source, now) {
39545
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(source?.recordedAt ?? "").trim());
39546
+ if (m) {
39547
+ const d = /* @__PURE__ */ new Date(`${m[1]}-${m[2]}-${m[3]}T00:00:00Z`);
39548
+ if (!isNaN(d.getTime()) && d.toISOString().slice(0, 10) === `${m[1]}-${m[2]}-${m[3]}`) return { date: d.toISOString().slice(0, 10), fromRecording: true };
39549
+ }
39550
+ return { date: now.toISOString().slice(0, 10), fromRecording: false };
39551
+ }
39552
+ function confirmList(held) {
39553
+ return held.map((h) => {
39554
+ const saved = /already has "([^"]*)" saved/.exec(h.because)?.[1];
39555
+ return `${ASK.get(h.questionId) ?? h.questionId} \u2192 ${h.value} (their words: ${q2(h.quote)})${saved !== void 0 ? ` [replaces the saved "${saved}"]` : ""}`;
39556
+ });
39557
+ }
39558
+ function analysisMarkdown(a, level) {
39559
+ return [
39560
+ `${level} What they said they want`,
39561
+ bullet(a.saidTheyWant),
39562
+ "",
39563
+ `${level} What they actually need`,
39564
+ bullet(a.actuallyNeed.map((p) => `${stop(p.point)} They said: ${q2(p.quote)}`)),
39565
+ "",
39566
+ `${level} Problems they did not name`,
39567
+ bullet(a.unnamedProblems.map((p) => `${stop(p.point)} They said: ${q2(p.quote)}`)),
39568
+ "",
39569
+ `${level} Recommended solutions`,
39570
+ bullet(a.recommendedSolutions.map((s) => `**${s.solution}** (${s.kind}).${s.why ? ` ${stop(s.why)}` : ""} They said: ${q2(s.quote)}`)),
39571
+ "",
39572
+ `${level} Questions for the next call`,
39573
+ bullet(a.nextCallQuestions)
39574
+ ].join("\n");
39575
+ }
39576
+ function buildClientFile(args) {
39577
+ const client = safeName(args.clientName) ?? "Unnamed client";
39578
+ const folderParts = [...CLIENT_FILES_ROOT, client];
39579
+ const recorder = args.source?.recorder ?? "Call";
39580
+ const title2 = args.source?.title?.trim() || "call";
39581
+ const stem = safeName(`${args.date} ${recorder} ${title2}`, 120) ?? `${args.date} call`;
39582
+ const name = `${stem}.md`;
39583
+ const transcriptName = `${stem} transcript.md`;
39584
+ const ask = (id) => ASK.get(id) ?? id;
39585
+ const src = args.source ? describeSource({ ...args.source, recordedAt: args.date }) : "Not recorded (no source was given)";
39586
+ const call2 = [
39587
+ `# Call with ${client}, ${args.date}`,
39588
+ "",
39589
+ `Source: ${src}.`,
39590
+ `Assessment: ${args.saveLine}`,
39591
+ "",
39592
+ "## Answers saved or ready to save",
39593
+ bullet(args.gated.accepted.map((a) => `${ask(a.questionId)} **${a.value}**${args.confirmedByOwner?.includes(a.questionId) ? " (confirmed by you)" : ""}. They said: ${q2(a.quote)}`)),
39594
+ "",
39595
+ "## Waiting for your confirmation",
39596
+ ...args.gated.held.length ? [confirmList(args.gated.held).map((l) => `- ${l}`).join("\n"), "", CONFIRM_INSTRUCTION] : ["- (none)"],
39597
+ "",
39598
+ "## Thrown out",
39599
+ bullet(args.gated.rejected.map((r) => `${ask(r.questionId)} ${r.value}: ${r.because}`)),
39600
+ "",
39601
+ ...args.analysis ? [
39602
+ "## Needs analysis (for you, not for the client)",
39603
+ "",
39604
+ analysisMarkdown(args.analysis.kept, "###"),
39605
+ "",
39606
+ ...args.analysis.rejected.length ? [
39607
+ "### Left out: the quote was not in the transcript",
39608
+ bullet(args.analysis.rejected.map((r) => `${r.item || "(empty)"}: ${r.why}`)),
39609
+ ""
39610
+ ] : []
39611
+ ] : [],
39612
+ "## Transcript",
39613
+ "",
39614
+ `Kept in its own file: Calls/${transcriptName}. If that file was not written, the recording in ${args.source?.recorder ?? "the recorder"} is the copy.`,
39615
+ ""
39616
+ ].join("\n");
39617
+ const transcript = [
39618
+ `# Transcript: call with ${client}, ${args.date}`,
39619
+ "",
39620
+ `Source: ${src}.`,
39621
+ "",
39622
+ args.transcript.trim(),
39623
+ ""
39624
+ ].join("\n");
39625
+ const understanding = [
39626
+ "",
39627
+ `## ${args.date}: ${recorder} call${args.source?.title?.trim() ? `, ${q2(args.source.title)}` : ""}`,
39628
+ "",
39629
+ ...args.analysis ? [analysisMarkdown(args.analysis.kept, "###"), ""] : ["No needs analysis was written for this call.", ""],
39630
+ `Assessment: ${args.saveLine}`,
39631
+ `Full call: Calls/${name}`,
39632
+ ""
39633
+ ].join("\n");
39634
+ return {
39635
+ clientName: client,
39636
+ folder: folderParts.join("/"),
39637
+ folderParts,
39638
+ call: { folderParts: [...folderParts, "Calls"], name, mode: "create-new", content: call2 },
39639
+ transcript: { folderParts: [...folderParts, "Calls"], name: transcriptName, mode: "create-new", optional: true, characters: args.transcript.trim().length, content: transcript },
39640
+ understanding: {
39641
+ folderParts,
39642
+ name: "Client understanding.md",
39643
+ mode: "append",
39644
+ headerIfNew: `# Client understanding: ${client}
39645
+
39646
+ What we know about this client, newest call last. Advice for the agency, never sent to the client.
39647
+ `,
39648
+ append: understanding
39649
+ },
39650
+ howToWrite: `Search for each folder in folderParts before creating it, one level at a time, and use the one that exists; create it only when the search finds none, and never make a second copy of the tree. Write call.content as a NEW file named call.name in the Calls folder; if that name is taken, add " (2)" rather than replacing it. Write transcript.content as its own new file, transcript.name, beside it; it is optional: when it is too large to write in one go (see transcript.characters), skip it, and a link to the recording or the recorder's own copy is enough. For understanding: read "Client understanding.md", add understanding.append at the end, write it back, then read it again and check every earlier dated section is still there. If it does not exist yet, create it with headerIfNew followed by the append. Never drop what was there: it is the running record of every call.`
39651
+ };
39652
+ }
39653
+ function inside(base, p) {
39654
+ return p.startsWith(base.endsWith(path32.sep) ? base : base + path32.sep);
39655
+ }
39656
+ function writeClientFileLocally(baseDir, file2) {
39657
+ try {
39658
+ if (!path32.isAbsolute(baseDir)) return { written: false, error: `the client files folder must be a full path, not "${baseDir}".` };
39659
+ const base = path32.resolve(baseDir);
39660
+ let st;
39661
+ try {
39662
+ st = fs34.statSync(base);
39663
+ } catch {
39664
+ return { written: false, error: `the client files folder ${base} does not exist. If it is your Google Drive folder, Google Drive for desktop needs to be installed and signed in.` };
39665
+ }
39666
+ if (!st.isDirectory()) return { written: false, error: `${base} is not a folder.` };
39667
+ const realBase = fs34.realpathSync(base);
39668
+ const callsDir = path32.resolve(base, ...file2.call.folderParts);
39669
+ const clientDir = path32.resolve(base, ...file2.understanding.folderParts);
39670
+ if (!inside(base, callsDir) || !inside(base, clientDir)) return { written: false, error: "the client folder name would land outside the client files folder." };
39671
+ let cur = base;
39672
+ for (const part of file2.call.folderParts) {
39673
+ cur = path32.join(cur, part);
39674
+ let here;
39675
+ try {
39676
+ here = fs34.lstatSync(cur);
39677
+ } catch {
39678
+ }
39679
+ if (here && (here.isSymbolicLink() || !here.isDirectory())) {
39680
+ return { written: false, error: `${cur} is a link or a file, not a folder, so nothing was written.` };
39681
+ }
39682
+ if (!here) fs34.mkdirSync(cur, { mode: 448 });
39683
+ }
39684
+ if (!inside(realBase, fs34.realpathSync(callsDir))) return { written: false, error: "the client folder resolves outside the client files folder, so nothing was written." };
39685
+ const stem = file2.call.name.replace(/\.md$/, "");
39686
+ const tStem = file2.transcript.name.replace(/\.md$/, "");
39687
+ let callFile = "", transcriptFile = "";
39688
+ for (let n = 1; n <= 50 && !callFile; n++) {
39689
+ const suffix = n === 1 ? "" : ` (${n})`;
39690
+ const callPath = path32.join(callsDir, `${stem}${suffix}.md`);
39691
+ const tName = `${tStem.slice(0, tStem.length - " transcript".length)}${suffix} transcript.md`;
39692
+ const tPath = path32.join(callsDir, tName);
39693
+ if (fs34.existsSync(callPath) || fs34.existsSync(tPath)) continue;
39694
+ try {
39695
+ fs34.writeFileSync(tPath, file2.transcript.content, { encoding: "utf8", flag: "wx", mode: 384 });
39696
+ fs34.writeFileSync(callPath, file2.call.content.split(`Calls/${file2.transcript.name}`).join(`Calls/${tName}`), { encoding: "utf8", flag: "wx", mode: 384 });
39697
+ callFile = callPath;
39698
+ transcriptFile = tPath;
39699
+ } catch (e) {
39700
+ if (e.code !== "EEXIST") throw e;
39701
+ }
39702
+ }
39703
+ if (!callFile) return { written: false, error: `fifty calls named "${stem}" already exist; nothing was overwritten.` };
39704
+ fs34.chmodSync(callFile, 384);
39705
+ fs34.chmodSync(transcriptFile, 384);
39706
+ const understandingFile = path32.join(clientDir, file2.understanding.name);
39707
+ let created = false;
39708
+ try {
39709
+ fs34.writeFileSync(understandingFile, file2.understanding.headerIfNew + file2.understanding.append, { encoding: "utf8", flag: "wx", mode: 384 });
39710
+ created = true;
39711
+ } catch (e) {
39712
+ if (e.code !== "EEXIST") throw e;
39713
+ }
39714
+ if (!created) {
39715
+ const st2 = fs34.lstatSync(understandingFile);
39716
+ if (st2.isSymbolicLink() || !st2.isFile() || st2.nlink > 1) return { written: false, error: `${understandingFile} is a link, not a plain file, so it was not appended to. The call file was written: ${callFile}` };
39717
+ fs34.appendFileSync(understandingFile, file2.understanding.append, { encoding: "utf8", mode: 384 });
39718
+ }
39719
+ fs34.chmodSync(understandingFile, 384);
39720
+ return { written: true, callFile, transcriptFile, understandingFile };
39721
+ } catch (e) {
39722
+ return { written: false, error: `could not write the client file: ${e?.message ?? String(e)}` };
39723
+ }
39724
+ }
39725
+ function contactNames(raw) {
39726
+ const c = raw?.contact ?? {};
39727
+ const s = (k) => typeof c[k] === "string" && c[k].trim() ? c[k].trim() : "";
39728
+ const name = s("name") || [s("firstName"), s("lastName")].filter(Boolean).join(" ") || s("contactName");
39729
+ return { name: name || null, business: s("companyName") || null };
39730
+ }
39731
+ async function withContactLock(key, fn) {
39732
+ const before = contactLocks.get(key) ?? Promise.resolve();
39733
+ let release;
39734
+ const mine = before.then(() => new Promise((r) => {
39735
+ release = r;
39736
+ }));
39737
+ contactLocks.set(key, mine);
39738
+ await before;
39739
+ try {
39740
+ return await fn();
39741
+ } finally {
39742
+ release();
39743
+ if (contactLocks.get(key) === mine) contactLocks.delete(key);
39744
+ }
39745
+ }
39746
+ async function readStrict(client, contactId) {
39747
+ const raw = await client.get(`/contacts/${encodeURIComponent(contactId)}/notes`);
39748
+ return pickLatestStrict(raw?.notes ?? []);
39749
+ }
39750
+ async function saveToContact(input, deps, gated, source) {
39751
+ const contactId = input.contactId;
39752
+ let effective = gated;
39753
+ const out = {
39754
+ status: "NOT SAVED",
39755
+ contactId,
39756
+ contactName: null,
39757
+ businessName: null,
39758
+ savedAnswers: 0,
39759
+ confirmedByOwner: [],
39760
+ confirmationsNotUsed: [],
39761
+ confirmedOnContact: [],
39762
+ reason: null,
39763
+ answersOnContact: null,
39764
+ notesWritten: 0,
39765
+ opportunityNote: null
39766
+ };
39767
+ const done = (o) => ({ outcome: { ...out, ...o }, effective });
39768
+ const fail = (reason3) => done({ status: "NOT SAVED", savedAnswers: 0, reason: reason3 });
39769
+ const msg3 = (e) => e?.message ?? String(e);
39770
+ if (input.existing && Object.keys(input.existing).length) return fail(EXISTING_WITH_CONTACT);
39771
+ try {
39772
+ const who = contactNames(await deps.client.get(`/contacts/${encodeURIComponent(contactId)}`));
39773
+ out.contactName = who.name;
39774
+ out.businessName = who.business;
39775
+ } catch (e) {
39776
+ return fail(`could not open contact ${contactId} in this account (${msg3(e)}).`);
39777
+ }
39778
+ let record;
39779
+ try {
39780
+ const got = await readStrict(deps.client, contactId);
39781
+ if (!got.clean) return fail(UNCLEAN_RECORD);
39782
+ record = got.record;
39783
+ } catch (e) {
39784
+ return fail(`could not read the answers already saved on this contact, and saving without them would hide them (${msg3(e)}).`);
39785
+ }
39786
+ if (record) out.businessName = out.businessName ?? record.state.biz;
39787
+ const sameValue = (a, b) => {
39788
+ const na = numericValue(a), nb = numericValue(b);
39789
+ return a.trim() === b.trim() || Boolean(na && nb && na.n === nb.n && na.percent === nb.percent);
39790
+ };
39791
+ const confirmedIds = /* @__PURE__ */ new Set();
39792
+ const heldAfterConfirm = gated.held.filter((h) => {
39793
+ const c = (input.confirmed ?? []).find((x) => String(x?.questionId) === h.questionId);
39794
+ if (c && sameValue(String(c.value), h.value)) {
39795
+ confirmedIds.add(h.questionId);
39796
+ return false;
39797
+ }
39798
+ return true;
39799
+ });
39800
+ for (const c of input.confirmed ?? []) {
39801
+ const id = String(c?.questionId), value = String(c?.value ?? "");
39802
+ if (confirmedIds.has(id)) continue;
39803
+ const held = gated.held.find((h) => h.questionId === id);
39804
+ out.confirmationsNotUsed.push({ questionId: id, 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 === id) ? "that answer was thrown out: its quote is not in the transcript." : "no answer to this question is waiting for confirmation." });
39805
+ }
39806
+ const candidates = [...gated.accepted, ...gated.held.filter((h) => confirmedIds.has(h.questionId)).map(({ because: _b, ...a }) => a)];
39807
+ const prior = record?.state.ans ?? {};
39808
+ const replace = new Set(input.proposed.filter((p) => p?.replace === true).map((p) => String(p.questionId)));
39809
+ const conflicts = [];
39810
+ const accepted = candidates.filter((a) => {
39811
+ const had = prior[a.questionId];
39812
+ if (had === void 0 || String(had).trim() === "" || String(had) === a.value || replace.has(a.questionId)) return true;
39813
+ conflicts.push({ ...a, because: `the contact already has "${had}" saved for this question and this call says "${a.value}". Confirm before it replaces the saved answer (send it again with replace: true).` });
39814
+ return false;
39815
+ });
39816
+ out.confirmedByOwner = accepted.filter((a) => confirmedIds.has(a.questionId)).map((a) => a.questionId);
39817
+ effective = { ...gated, accepted, held: [...heldAfterConfirm, ...conflicts], patch: Object.fromEntries(accepted.map((a) => [a.questionId, a.value])) };
39818
+ if (accepted.length === 0) {
39819
+ return done({ status: "NOTHING TO SAVE", reason: effective.held.length ? "every answer is waiting for your OK (numbers always do), so nothing new was written yet." : "no new answer passed the quote check, so nothing new was written.", answersOnContact: record?.state.ans ?? null });
39820
+ }
39821
+ const biz = record?.state.biz || clean(input.businessName) || out.businessName || out.contactName;
39822
+ if (!biz) return fail("neither the contact nor a saved assessment has a business name. Give businessName and try again.");
39823
+ out.businessName = out.businessName ?? biz;
39824
+ const skip = { ...record?.state.skip ?? {} };
39825
+ for (const a of accepted) delete skip[a.questionId];
39826
+ const expected = { ...prior, ...effective.patch };
39827
+ const confirmedAll = [.../* @__PURE__ */ new Set([
39828
+ ...(record?.confirmedByOwner ?? []).filter((id) => !(id in effective.patch) || out.confirmedByOwner.includes(id)),
39829
+ ...out.confirmedByOwner
39830
+ ])];
39831
+ const state = { ...record?.state ?? { asm: {} }, biz, ans: expected, skip, asm: record?.state.asm ?? {} };
39832
+ let failure2 = null;
39833
+ try {
39834
+ const saved = await saveAssessment(deps.client, deps.locationId, {
39835
+ assessment: state,
39836
+ contactId,
39837
+ source,
39838
+ createPipeline: input.createPipeline,
39839
+ confirmedByOwner: confirmedAll
39840
+ });
39841
+ if (!saved.saved) failure2 = saved.error ?? "GoHighLevel did not accept the save.";
39842
+ out.notesWritten = saved.notesWritten ?? 0;
39843
+ out.opportunityNote = saved.opportunityNote ?? null;
39844
+ } catch (e) {
39845
+ failure2 = `GoHighLevel reported an error (${msg3(e)}).`;
39846
+ }
39847
+ let back;
39848
+ try {
39849
+ const got = await readStrict(deps.client, contactId);
39850
+ back = got.clean ? got.record : null;
39851
+ } catch (e) {
39852
+ return done({ status: "NOT CONFIRMED", reason: `${failure2 ? failure2 + " " : ""}The contact could not be read back (${msg3(e)}). Treat the answers as not saved until you open the contact.` });
39853
+ }
39854
+ const off = back ? same(back.state.ans, expected) : null;
39855
+ const marks = back?.confirmedByOwner ?? [];
39856
+ if (off && off.length === 0 && marks.length === confirmedAll.length && confirmedAll.every((id) => marks.includes(id))) {
39857
+ return done({ status: "SAVED", savedAnswers: accepted.length, answersOnContact: back.state.ans, confirmedOnContact: marks, reason: failure2 ? `${failure2} The answers did land and read back correctly.` : null });
39858
+ }
39859
+ const unchanged = back ? same(back.state.ans, prior).length === 0 : !record;
39860
+ if (failure2 && unchanged) return done({ status: "NOT SAVED", savedAnswers: 0, reason: failure2, answersOnContact: back?.state.ans ?? null });
39861
+ return done({
39862
+ status: "NOT CONFIRMED",
39863
+ answersOnContact: back?.state.ans ?? null,
39864
+ reason: `${failure2 ? failure2 + " " : ""}Reading the contact back ${back ? `did not match what was saved (${off.length ? off.join(", ") : "the confirmed-by-you marks"}); the contact may have changed at the same time` : "found no complete assessment"}. Treat the answers as not saved until you open the contact.`
39865
+ });
39866
+ }
39867
+ async function processClientCall(input, deps) {
39868
+ let gated = gateExtraction(input.transcript, input.proposed, QUESTION_UNITS);
39869
+ const legacyOf = (gated2) => ({
39870
+ applied: gated2.accepted.length,
39871
+ answers: mergeAccepted(input.existing ?? {}, gated2),
39872
+ confirmBeforeTheseCount: gated2.held.map((h) => ({
39873
+ question: ASK.get(h.questionId) ?? h.questionId,
39874
+ proposedValue: h.value,
39875
+ theyActuallySaid: h.quote,
39876
+ howItWasWorkedOut: h.reasoning ?? null,
39877
+ why: h.because
39878
+ })),
39879
+ thrownOut: gated2.rejected.map((r) => ({ questionId: r.questionId, value: r.value, why: r.because })),
39880
+ note: gated2.rejected.some((r) => /does not appear in the transcript/.test(r.because)) ? "One or more answers cited words that are not in the transcript. Those were discarded, not stored. That check is the only thing standing between a recording and an invented figure in a client's report." : null
39881
+ });
39882
+ const legacy = legacyOf(gated);
39883
+ if (!input.contactId && !input.analysis && !input.source) return legacy;
39884
+ const now = deps.now();
39885
+ const { date, fromRecording } = callDate(input.source, now);
39886
+ const noteSource = input.source ? { recorder: input.source.recorder, recordedAt: date, title: input.source.title?.trim() || void 0, fetchedBy: input.source.fetchedBy } : void 0;
39887
+ const saving = input.contactId ? await withContactLock(`${deps.locationId}:${input.contactId}`, () => saveToContact(input, deps, gated, noteSource)) : null;
39888
+ const save = saving?.outcome ?? null;
39889
+ if (saving) gated = saving.effective;
39890
+ const analysis = input.analysis ? gateAnalysis(input.transcript, input.analysis) : null;
39891
+ const counts = `${plural(gated.held.length, "answer needs", "answers need")} your confirmation, ${plural(gated.rejected.length, "was", "were")} thrown out`;
39892
+ const who = save ? `${save.businessName ?? save.contactName ?? "the contact"}${save.contactName && save.businessName ? ` (${save.contactName})` : ""}, contact ${save.contactId}` : "";
39893
+ let reply;
39894
+ let saveLine;
39895
+ if (!save) {
39896
+ reply = `NOT SAVED: no contact was named, so these answers are only proposed. ${gated.accepted.length} passed the check, ${counts}. Call again with the client's contactId to save them.`;
39897
+ saveLine = "not saved to GoHighLevel (no contact was named).";
39898
+ } else if (save.status === "SAVED") {
39899
+ const byYou = save.confirmedByOwner.length ? ` (${save.confirmedByOwner.length} confirmed by you)` : "";
39900
+ reply = `SAVED ${plural(save.savedAnswers, "answer", "answers")}${byYou} to ${who}. ${counts[0].toUpperCase()}${counts.slice(1)}.${save.reason ? ` Note: ${save.reason}` : ""}`;
39901
+ saveLine = `saved ${plural(save.savedAnswers, "answer", "answers")}${byYou} on the GoHighLevel contact ${save.contactId}.`;
39902
+ } else if (save.status === "NOTHING TO SAVE") {
39903
+ const why = gated.held.length ? "every answer is waiting for your OK (numbers always do, and any that differ from a saved answer)" : "no answer passed the quote check";
39904
+ reply = `NOTHING TO SAVE for ${who}: ${why}. ${counts[0].toUpperCase()}${counts.slice(1)}.`;
39905
+ saveLine = `nothing new saved (${why}).`;
39906
+ } else {
39907
+ reply = `${save.status} for ${who}: ${save.reason} ${save.status === "NOT SAVED" ? "Nothing from this call is on the contact." : ""} Of the proposed answers, ${counts}.`.replace(/\s+/g, " ");
39908
+ saveLine = `${save.status}. ${save.reason}`;
39909
+ }
39910
+ if (save?.opportunityNote) reply += ` ${save.opportunityNote}`;
39911
+ if (save?.confirmationsNotUsed.length) reply += ` ${plural(save.confirmationsNotUsed.length, "confirmation was", "confirmations were")} not used: ${save.confirmationsNotUsed.map((c) => `${c.questionId} (${c.why})`).join("; ")}`;
39912
+ if (analysis?.rejected.length) reply += ` ${plural(analysis.rejected.length, "analysis point was", "analysis points were")} left out because the quote is not in the transcript.`;
39913
+ if (gated.held.length) reply += `
39914
+
39915
+ To confirm:
39916
+ ${confirmList(gated.held).map((l) => `- ${l}`).join("\n")}
39917
+ ${CONFIRM_INSTRUCTION}`;
39918
+ const clientName = save?.businessName ?? save?.contactName ?? clean(input.businessName);
39919
+ const clientFile = clientName ? buildClientFile({ clientName, date, source: input.source, gated, analysis, transcript: input.transcript, saveLine, confirmedByOwner: save?.confirmedByOwner }) : null;
39920
+ const localCopy = clientFile && deps.clientFilesDir ? writeClientFileLocally(deps.clientFilesDir, clientFile) : null;
39921
+ const { applied, ...legacyRest } = legacyOf(gated);
39922
+ return {
39923
+ // Never hand back unchecked values beside a failed save (review D-3).
39924
+ ...save ? (({ answers: _unchecked, ...rest }) => rest)(legacyRest) : { applied, ...legacyRest },
39925
+ ...save?.answersOnContact ? { answers: save.answersOnContact } : {},
39926
+ reply,
39927
+ toConfirm: gated.held.map((h) => ({ questionId: h.questionId, question: ASK.get(h.questionId) ?? h.questionId, value: h.value, theirWords: h.quote, replacesSaved: /already has "/.test(h.because) })),
39928
+ saved: save,
39929
+ source: noteSource ? { ...noteSource, dateFromRecording: fromRecording } : null,
39930
+ analysis: analysis ? { ...analysis.kept, leftOutForQuote: analysis.rejected, forTheOwnerOnly: true } : null,
39931
+ clientFile,
39932
+ clientFileNote: clientFile ? null : "No client name was known (no contact, no businessName), so no client file entry was made.",
39933
+ localCopy
39934
+ };
39935
+ }
39936
+ var fs34, path32, RECORDERS, SOLUTION_KINDS, clean, ANALYSIS_MIN_WORDS, CLIENT_FILES_ROOT, WINDOWS_RESERVED, bullet, q2, CONFIRM_INSTRUCTION, stop, EXISTING_WITH_CONTACT, UNCLEAN_RECORD, contactLocks, same, plural;
39937
+ var init_client_calls = __esm({
39938
+ "src/command-os/client-calls.ts"() {
39939
+ "use strict";
39940
+ fs34 = __toESM(require("node:fs"));
39941
+ path32 = __toESM(require("node:path"));
39942
+ init_transcript();
39943
+ init_words();
39944
+ init_store3();
39945
+ init_assessment();
39946
+ RECORDERS = ["Plaud", "Zoom", "Pocket", "Fathom", "Other"];
39947
+ SOLUTION_KINDS = ["GoHighLevel build", "AI tool", "custom build", "process"];
39948
+ clean = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
39949
+ ANALYSIS_MIN_WORDS = 3;
39950
+ CLIENT_FILES_ROOT = ["Command OS", "Clients"];
39951
+ WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
39952
+ bullet = (xs) => xs.length ? xs.map((x) => `- ${x}`).join("\n") : "- (none)";
39953
+ q2 = (s) => `"${s.replace(/\s+/g, " ").trim()}"`;
39954
+ CONFIRM_INSTRUCTION = "Reply 'confirm all', or 'confirm all except \u2026' naming the ones to leave out.";
39955
+ stop = (s) => s && !/[.!?]["')\]]?$/.test(s) ? `${s}.` : s;
39956
+ EXISTING_WITH_CONTACT = "existing cannot be used with a contact: the contact's saved answers are the starting point, and only answers quoted from this call are added. Leave existing out and call again.";
39957
+ UNCLEAN_RECORD = "the newest saved assessment on this contact is incomplete or could not be read, so nothing was changed. It may have been edited by hand, or a save may have stopped partway; saving the assessment again with save_assessment writes a complete one, and then this call can be added.";
39958
+ contactLocks = /* @__PURE__ */ new Map();
39959
+ same = (a, b) => {
39960
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
39961
+ return [...keys].filter((k) => String(a[k] ?? "") !== String(b[k] ?? ""));
39962
+ };
39963
+ plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
39964
+ }
39965
+ });
39966
+
39382
39967
  // src/tools/assessment.ts
39383
39968
  function assessmentValidationMessage(error) {
39384
39969
  if (!(error instanceof import_zod61.z.ZodError)) return void 0;
@@ -39461,19 +40046,34 @@ async function saveAssessment(client, locationId2, args, onOpportunityNoteFailur
39461
40046
  const state = normalise(args.assessment);
39462
40047
  const computed = computedFor(state);
39463
40048
  const stage = stageFor({ verdict: computed.verdict, proposalSent: args.proposalSent, outcome: args.outcome });
39464
- const { payload: contactBody, dedupable } = buildContact(state, stage, {
40049
+ const { payload: contactBody, dedupable: dedupableByDetails } = buildContact(state, stage, {
39465
40050
  name: args.contactName,
39466
40051
  email: args.email,
39467
40052
  phone: args.phone
39468
40053
  });
39469
- const upserted = await client.post("/contacts/upsert", {
39470
- body: { ...contactBody, locationId: locationId2 }
39471
- });
39472
- const contactId = upserted?.contact?.id ?? upserted?.id;
40054
+ let contactId;
40055
+ if (args.contactId) {
40056
+ const found = await client.get(`/contacts/${encodeURIComponent(args.contactId)}`);
40057
+ const existingContact = found?.contact;
40058
+ contactId = existingContact?.id;
40059
+ if (!contactId) {
40060
+ return { saved: false, error: `GoHighLevel has no contact ${args.contactId} in this account, so nothing was written.` };
40061
+ }
40062
+ if (!existingContact?.companyName?.trim() && contactBody.companyName) {
40063
+ await client.put(`/contacts/${encodeURIComponent(contactId)}`, { body: { companyName: contactBody.companyName } });
40064
+ }
40065
+ await client.post(`/contacts/${encodeURIComponent(contactId)}/tags`, { body: { tags: contactBody.tags } });
40066
+ } else {
40067
+ const upserted = await client.post("/contacts/upsert", {
40068
+ body: { ...contactBody, locationId: locationId2 }
40069
+ });
40070
+ contactId = upserted?.contact?.id ?? upserted?.id;
40071
+ }
39473
40072
  if (!contactId) {
39474
40073
  return { saved: false, error: "GoHighLevel did not return a contact id, so nothing was written. Nothing partial was left behind." };
39475
40074
  }
39476
- const bodies = buildNoteBodies(buildNote(state, computed, LOOKUP, today()));
40075
+ const dedupable = dedupableByDetails || Boolean(args.contactId);
40076
+ const bodies = buildNoteBodies(buildNote(state, computed, LOOKUP, today(), args.source, args.confirmedByOwner));
39477
40077
  const noteIds = [];
39478
40078
  for (const body of bodies) {
39479
40079
  const note = await client.post(`/contacts/${contactId}/notes`, { body: { body } });
@@ -39645,33 +40245,56 @@ function registerAssessmentTools(server2, client) {
39645
40245
  safeTool(
39646
40246
  server2,
39647
40247
  "read_assessment_transcript",
39648
- "Turn a recorded conversation into assessment answers, WITHOUT letting anything into the record that the owner did not say. You read the transcript and propose answers; each one must carry the verbatim words it came from. This checks every quote actually appears in the transcript and throws out the ones that do not, and holds back anything you worked out rather than heard so the operator can confirm it. Use it after a coffee-shop meeting, a Zoom call or a Plaud recording.",
40248
+ `Bring a recorded client call (Plaud, Zoom, Pocket, Fathom, or any transcript file the owner attaches) into the client's assessment, their needs analysis and their client file. The flow: 1) get the transcript yourself, with your own Plaud or Zoom connection or from the attached file; 2) propose assessment answers, each with the client's words copied VERBATIM from the transcript as the quote; for a text question the value is the client's own words taken from that quote, and for a number it is the number they said, written in digits (Plaud writes "a hundred percent"; send "100%"); 3) write the needs analysis, every need, unnamed problem and recommended solution tied to a verbatim quote of at least three words; 4) call this with the client's contactId and the source; 5) write the returned clientFile to the owner's Google Drive with your Drive connection, following clientFile.howToWrite; 6) show the owner the reply as given, including the "To confirm" list. EVERY NUMBER waits for the owner's OK and never saves on its own, because no word check can tell a figure that answers the question from one said in passing. Text answers in the client's own words save straight away. When the owner answers the list ("confirm all", or "confirm all except ..."), call again with the same transcript, proposed and contactId, and confirmed holding exactly the approved items (questionId and value from toConfirm); for an item marked replacesSaved, also set replace: true on that proposed answer. Never put an item in confirmed that the owner has not approved in this conversation. Drive allows two folders with the same name, so SEARCH for each folder in the path (and for the client's folder above all) before creating it, and never create a second tree. To append to "Client understanding": read it, add the new dated section at the end, write it back, then read it again and confirm every earlier section is still there. Answers and analysis points whose quote is not in the transcript are thrown out. With a contactId the saved answers are read back: the reply starts SAVED, NOT SAVED, NOT CONFIRMED or NOTHING TO SAVE, and only SAVED means they are on the contact. The needs analysis is advice for the owner and is never sent to the client. Without a contactId nothing is saved.`,
39649
40249
  {
39650
40250
  transcript: import_zod61.z.string().min(20).describe("The recording, as text. Paste the whole thing."),
39651
40251
  proposed: import_zod61.z.array(import_zod61.z.object({
39652
40252
  questionId: import_zod61.z.string().describe("The assessment question this answers."),
39653
40253
  value: import_zod61.z.string().describe("What to store, in the unit the question wants."),
39654
40254
  quote: import_zod61.z.string().describe("The owner's own words, VERBATIM from the transcript. Do not tidy it up \u2014 if it is not in the transcript the answer is thrown out."),
39655
- reasoning: import_zod61.z.string().optional().describe("How you got from the quote to the value, when they are not the same.")
40255
+ reasoning: import_zod61.z.string().optional().describe("How you got from the quote to the value, when they are not the same."),
40256
+ replace: import_zod61.z.boolean().optional().describe("Only after the owner confirmed it: let this answer replace a different one already saved on the contact.")
39656
40257
  })).describe("One entry per question you could answer. Leave out anything the conversation did not cover."),
39657
- existing: import_zod61.z.record(import_zod61.z.string(), import_zod61.z.union([import_zod61.z.string(), import_zod61.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them.")
40258
+ confirmed: import_zod61.z.array(import_zod61.z.object({
40259
+ questionId: import_zod61.z.string(),
40260
+ value: import_zod61.z.union([import_zod61.z.string(), import_zod61.z.number()]).describe("The exact value the owner said yes to.")
40261
+ })).optional().describe("Held answers the owner has said yes to, that specific value, in THIS conversation. Never add one the owner has not confirmed. Its quote must still be in the transcript, and it does not replace a different saved answer without replace: true."),
40262
+ existing: import_zod61.z.record(import_zod61.z.string(), import_zod61.z.union([import_zod61.z.string(), import_zod61.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them. Not with contactId: there the contact's saved answers are the starting point."),
40263
+ contactId: import_zod61.z.string().optional().describe("The client's GoHighLevel contact. Given, the checked answers are added to the assessment already saved on it and saved in this same call. Find it with search_contacts first."),
40264
+ locationId: import_zod61.z.string().optional().describe("The sub-account the contact is in. Defaults to the active one."),
40265
+ businessName: import_zod61.z.string().optional().describe("The client's business name, used when the contact and its saved assessment carry none."),
40266
+ createPipeline: import_zod61.z.boolean().optional().describe('Create the "Assessments" pipeline if it is missing. Only when the owner has said yes.'),
40267
+ source: import_zod61.z.object({
40268
+ recorder: import_zod61.z.enum(["Plaud", "Zoom", "Pocket", "Fathom", "Other"]).describe("What recorded the call."),
40269
+ recordedAt: import_zod61.z.string().describe("When the call happened, as an ISO date (2026-09-24) or timestamp, from the recording itself."),
40270
+ title: import_zod61.z.string().optional().describe("The recording's title, as the recorder shows it."),
40271
+ fetchedBy: import_zod61.z.enum(["claude", "upload"]).optional().describe("claude when you fetched it with your own connector, upload when the owner attached a file.")
40272
+ }).optional().describe("Where the transcript came from. Written onto the saved note and into the client file."),
40273
+ analysis: import_zod61.z.object({
40274
+ saidTheyWant: import_zod61.z.array(import_zod61.z.string()).optional().describe("What the client asked for, in plain words."),
40275
+ actuallyNeed: import_zod61.z.array(import_zod61.z.object({ point: import_zod61.z.string(), quote: import_zod61.z.string().describe("VERBATIM from the transcript.") })).optional().describe("What they really need, each with the words that show it."),
40276
+ unnamedProblems: import_zod61.z.array(import_zod61.z.object({ point: import_zod61.z.string(), quote: import_zod61.z.string().describe("VERBATIM from the transcript.") })).optional().describe("Problems they described without naming, each with the words that show it."),
40277
+ recommendedSolutions: import_zod61.z.array(import_zod61.z.object({
40278
+ solution: import_zod61.z.string(),
40279
+ kind: import_zod61.z.enum(["GoHighLevel build", "AI tool", "custom build", "process"]),
40280
+ why: import_zod61.z.string().describe("Why it fits this client."),
40281
+ quote: import_zod61.z.string().describe("VERBATIM: what they said that this answers.")
40282
+ })).optional().describe("What you would build or change, including things they did not ask for."),
40283
+ nextCallQuestions: import_zod61.z.array(import_zod61.z.string()).optional().describe("What to ask on the next call.")
40284
+ }).optional().describe("Your needs analysis of the call. For the owner only; it is never sent to the client. Every quote is checked against the transcript like the answers are.")
39658
40285
  },
39659
- async ({ transcript, proposed, existing }) => {
39660
- const gated = gateExtraction(transcript, proposed, QUESTION_UNITS);
39661
- const merged = mergeAccepted(existing ?? {}, gated);
39662
- return {
39663
- applied: gated.accepted.length,
39664
- answers: merged,
39665
- confirmBeforeTheseCount: gated.held.map((h) => ({
39666
- question: ASK.get(h.questionId) ?? h.questionId,
39667
- proposedValue: h.value,
39668
- theyActuallySaid: h.quote,
39669
- howItWasWorkedOut: h.reasoning ?? null,
39670
- why: h.because
39671
- })),
39672
- thrownOut: gated.rejected.map((r) => ({ questionId: r.questionId, value: r.value, why: r.because })),
39673
- note: gated.rejected.some((r) => /does not appear in the transcript/.test(r.because)) ? "One or more answers cited words that are not in the transcript. Those were discarded, not stored. That check is the only thing standing between a recording and an invented figure in a client's report." : null
39674
- };
40286
+ async (args) => {
40287
+ const [{ processClientCall: processClientCall2 }, { readAgencyProfile: readAgencyProfile2 }] = await Promise.all([
40288
+ Promise.resolve().then(() => (init_client_calls(), client_calls_exports)),
40289
+ Promise.resolve().then(() => (init_agency_profile(), agency_profile_exports))
40290
+ ]);
40291
+ const wantsFiles = Boolean(args.contactId || args.analysis || args.source);
40292
+ return processClientCall2(args, {
40293
+ client,
40294
+ locationId: args.contactId ? client.resolveLocationId(args.locationId) : "",
40295
+ now: () => /* @__PURE__ */ new Date(),
40296
+ clientFilesDir: wantsFiles ? readAgencyProfile2().clientFilesDir : void 0
40297
+ });
39675
40298
  }
39676
40299
  );
39677
40300
  safeTool(
@@ -39695,10 +40318,10 @@ function registerAssessmentTools(server2, client) {
39695
40318
  const r = compute(state.ans, state.skip, state.asm);
39696
40319
  const labels = {};
39697
40320
  for (const a of ASSUMPTIONS) labels[a[0]] = a[3];
39698
- const answered = Q.filter((q2) => {
39699
- const id = q2[0];
40321
+ const answered = Q.filter((q3) => {
40322
+ const id = q3[0];
39700
40323
  return state.skip[id] === true || state.ans[id] !== void 0 && String(state.ans[id]).trim() !== "";
39701
- }).map((q2) => state.skip[q2[0]] ? { ask: q2[2], unknown: true, unit: q2[4] } : { ask: q2[2], value: String(state.ans[q2[0]]), unit: q2[4] });
40324
+ }).map((q3) => state.skip[q3[0]] ? { ask: q3[2], unknown: true, unit: q3[4] } : { ask: q3[2], value: String(state.ans[q3[0]]), unit: q3[4] });
39702
40325
  const html2 = renderReport2({
39703
40326
  state,
39704
40327
  computed: r,
@@ -39759,16 +40382,15 @@ var init_assessment = __esm({
39759
40382
  init_tool_helpers();
39760
40383
  init_store3();
39761
40384
  init_engine();
39762
- init_transcript();
39763
40385
  init_report();
39764
40386
  import_node_crypto11 = require("node:crypto");
39765
40387
  import_node_fs6 = require("node:fs");
39766
40388
  import_node_os = require("node:os");
39767
40389
  import_node_path6 = require("node:path");
39768
- QUESTION_UNITS = new Map(Q.map((q2) => [q2[0], q2[4]]));
40390
+ QUESTION_UNITS = new Map(Q.map((q3) => [q3[0], q3[4]]));
39769
40391
  AREA_TITLE = new Map(AREAS.map((a) => [a[0], a[1]]));
39770
- ASK = new Map(Q.map((q2) => [q2[0], q2[2]]));
39771
- AREA_OF = new Map(Q.map((q2) => [q2[0], q2[1]]));
40392
+ ASK = new Map(Q.map((q3) => [q3[0], q3[2]]));
40393
+ AREA_OF = new Map(Q.map((q3) => [q3[0], q3[1]]));
39772
40394
  ASSUMPTION_LABEL = new Map(ASSUMPTIONS.map((a) => [a[0], a[1]]));
39773
40395
  LOOKUP = {
39774
40396
  ask: (id) => ASK.get(id),
@@ -39876,6 +40498,7 @@ __export(dashboard_exports, {
39876
40498
  INTAKE_OWN_MESSAGES: () => INTAKE_OWN_MESSAGES,
39877
40499
  INTAKE_UNREADABLE: () => INTAKE_UNREADABLE,
39878
40500
  NOT_SIGNED_IN_MSG: () => NOT_SIGNED_IN_MSG,
40501
+ PARKED_NOT_BUILT: () => PARKED_NOT_BUILT,
39879
40502
  SEAT_GUARDED_READS: () => SEAT_GUARDED_READS,
39880
40503
  SEAT_TOKEN_HEADER: () => SEAT_TOKEN_HEADER,
39881
40504
  STAGES: () => STAGES2,
@@ -39947,11 +40570,11 @@ function recoverStuckStages(state) {
39947
40570
  return { state: { ...state, clients }, recovered };
39948
40571
  }
39949
40572
  function cockpitStatePath() {
39950
- return path32.join(appDataDir(), "cockpit-state.json");
40573
+ return path33.join(appDataDir(), "cockpit-state.json");
39951
40574
  }
39952
40575
  function readCockpitState() {
39953
40576
  try {
39954
- const raw = JSON.parse(fs34.readFileSync(cockpitStatePath(), "utf8"));
40577
+ const raw = JSON.parse(fs35.readFileSync(cockpitStatePath(), "utf8"));
39955
40578
  if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
39956
40579
  } catch {
39957
40580
  }
@@ -39959,7 +40582,7 @@ function readCockpitState() {
39959
40582
  }
39960
40583
  function writeCockpitState(state) {
39961
40584
  ensureAppDataDir();
39962
- fs34.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
40585
+ fs35.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
39963
40586
  }
39964
40587
  function setStage(state, locationId2, stageIndex, status) {
39965
40588
  if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES2.length) throw new Error("bad stage index");
@@ -40103,11 +40726,11 @@ function startRun(locationId2, stage, onDone, meta = {}) {
40103
40726
  industry,
40104
40727
  manualObligations: stage === 4 ? readBuildObligations(locationId2) : void 0,
40105
40728
  intakeSummary: describeComposition({ industry, agency }),
40106
- customLabels: composed.filter((q2) => {
40107
- const base = INTAKE_QUESTIONS.find((b) => b.key === q2.key);
40108
- return base && base.label !== q2.label;
40109
- }).map((q2) => ({ key: q2.key, label: q2.label, from: INTAKE_QUESTIONS.find((b) => b.key === q2.key)?.label })),
40110
- removedKeys: INTAKE_QUESTIONS.filter((b) => !composed.some((q2) => q2.key === b.key)).map((b) => b.key),
40729
+ customLabels: composed.filter((q3) => {
40730
+ const base = INTAKE_QUESTIONS.find((b) => b.key === q3.key);
40731
+ return base && base.label !== q3.label;
40732
+ }).map((q3) => ({ key: q3.key, label: q3.label, from: INTAKE_QUESTIONS.find((b) => b.key === q3.key)?.label })),
40733
+ removedKeys: INTAKE_QUESTIONS.filter((b) => !composed.some((q3) => q3.key === b.key)).map((b) => b.key),
40111
40734
  // A build re-run reuses the approved plan already saved for this account (finding 10).
40112
40735
  // ...and only while it still builds the scope the member has confirmed now: a
40113
40736
  // changed selection composes the plan fresh from the preset (delta round 3, T-M4).
@@ -40290,7 +40913,7 @@ function notify(message) {
40290
40913
  `);
40291
40914
  try {
40292
40915
  ensureAppDataDir();
40293
- fs34.appendFileSync(path32.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
40916
+ fs35.appendFileSync(path33.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
40294
40917
  `);
40295
40918
  } catch {
40296
40919
  }
@@ -40360,20 +40983,20 @@ function withCockpitShell(page, screen, extraHead = "") {
40360
40983
  function clientDocsDir(locationId2, base = appDataDir()) {
40361
40984
  const safeLoc = String(locationId2).replace(/[^A-Za-z0-9_-]/g, "");
40362
40985
  if (!safeLoc) throw new Error("no client");
40363
- return path32.join(base, "clients", safeLoc, "documents");
40986
+ return path33.join(base, "clients", safeLoc, "documents");
40364
40987
  }
40365
40988
  function safeDocName(name) {
40366
- const cleaned = path32.basename(String(name ?? "")).replace(/[^A-Za-z0-9._ -]/g, "_").replace(/\s+/g, " ").trim();
40367
- const ext = path32.extname(cleaned).toLowerCase();
40989
+ const cleaned = path33.basename(String(name ?? "")).replace(/[^A-Za-z0-9._ -]/g, "_").replace(/\s+/g, " ").trim();
40990
+ const ext = path33.extname(cleaned).toLowerCase();
40368
40991
  if (!cleaned || cleaned.startsWith(".") || !DOC_EXTS.has(ext)) throw new Error("Only plain text (.txt, .md, .csv), .vtt and .srt files can be saved here.");
40369
40992
  const stem = cleaned.slice(0, cleaned.length - ext.length).slice(0, 120 - ext.length).trim();
40370
40993
  return `${stem || "document"}${ext}`;
40371
40994
  }
40372
40995
  function listClientDocs(locationId2, base = appDataDir()) {
40373
40996
  const dir = clientDocsDir(locationId2, base);
40374
- if (!fs34.existsSync(dir)) return [];
40375
- return fs34.readdirSync(dir).filter((f) => DOC_EXTS.has(path32.extname(f).toLowerCase())).map((f) => {
40376
- const st = fs34.statSync(path32.join(dir, f));
40997
+ if (!fs35.existsSync(dir)) return [];
40998
+ return fs35.readdirSync(dir).filter((f) => DOC_EXTS.has(path33.extname(f).toLowerCase())).map((f) => {
40999
+ const st = fs35.statSync(path33.join(dir, f));
40377
41000
  return { name: f, bytes: st.size, savedAt: st.mtime.toISOString() };
40378
41001
  }).sort((a, b) => b.savedAt.localeCompare(a.savedAt));
40379
41002
  }
@@ -40391,7 +41014,7 @@ function readFailureText(e) {
40391
41014
  function logQuietly(message) {
40392
41015
  try {
40393
41016
  ensureAppDataDir();
40394
- fs34.appendFileSync(path32.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
41017
+ fs35.appendFileSync(path33.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
40395
41018
  `);
40396
41019
  } catch {
40397
41020
  }
@@ -40464,40 +41087,40 @@ function newClientFailure(error, surface = "command-os", account2) {
40464
41087
  }
40465
41088
  function insideDir(dir, p) {
40466
41089
  try {
40467
- const real = fs34.realpathSync(p);
40468
- const base = fs34.realpathSync(dir);
40469
- return real === base || real.startsWith(base + path32.sep);
41090
+ const real = fs35.realpathSync(p);
41091
+ const base = fs35.realpathSync(dir);
41092
+ return real === base || real.startsWith(base + path33.sep);
40470
41093
  } catch {
40471
41094
  return false;
40472
41095
  }
40473
41096
  }
40474
41097
  function uniqueDocName(dir, name) {
40475
- const ext = path32.extname(name), stem = name.slice(0, name.length - ext.length);
41098
+ const ext = path33.extname(name), stem = name.slice(0, name.length - ext.length);
40476
41099
  let candidate = name;
40477
- for (let n = 2; fs34.existsSync(path32.join(dir, candidate)); n++) candidate = `${stem} (${n})${ext}`;
41100
+ for (let n = 2; fs35.existsSync(path33.join(dir, candidate)); n++) candidate = `${stem} (${n})${ext}`;
40478
41101
  return candidate;
40479
41102
  }
40480
41103
  function writeDocAtomically(dir, name, bytes) {
40481
- fs34.mkdirSync(dir, { recursive: true, mode: 448 });
41104
+ fs35.mkdirSync(dir, { recursive: true, mode: 448 });
40482
41105
  const finalName = uniqueDocName(dir, name);
40483
- const tmp = path32.join(dir, `.${finalName}.${process.pid}.tmp`);
40484
- const fd = fs34.openSync(tmp, fs34.constants.O_WRONLY | fs34.constants.O_CREAT | fs34.constants.O_EXCL | (fs34.constants.O_NOFOLLOW ?? 0), 384);
41106
+ const tmp = path33.join(dir, `.${finalName}.${process.pid}.tmp`);
41107
+ const fd = fs35.openSync(tmp, fs35.constants.O_WRONLY | fs35.constants.O_CREAT | fs35.constants.O_EXCL | (fs35.constants.O_NOFOLLOW ?? 0), 384);
40485
41108
  try {
40486
- fs34.writeSync(fd, bytes);
40487
- fs34.fsyncSync(fd);
41109
+ fs35.writeSync(fd, bytes);
41110
+ fs35.fsyncSync(fd);
40488
41111
  } finally {
40489
- fs34.closeSync(fd);
41112
+ fs35.closeSync(fd);
40490
41113
  }
40491
- const dest = path32.join(dir, finalName);
41114
+ const dest = path33.join(dir, finalName);
40492
41115
  try {
40493
- if (fs34.lstatSync(dest).isSymbolicLink()) {
40494
- fs34.unlinkSync(tmp);
41116
+ if (fs35.lstatSync(dest).isSymbolicLink()) {
41117
+ fs35.unlinkSync(tmp);
40495
41118
  throw new Error("that name is taken by a link");
40496
41119
  }
40497
41120
  } catch (e) {
40498
41121
  if (e.code !== "ENOENT") throw e;
40499
41122
  }
40500
- fs34.renameSync(tmp, dest);
41123
+ fs35.renameSync(tmp, dest);
40501
41124
  return finalName;
40502
41125
  }
40503
41126
  function readableAccount(locationId2, roster = currentRosterRef(), state = readCockpitState()) {
@@ -41252,6 +41875,9 @@ function preflightCard(c){const box=document.createElement("section");box.classN
41252
41875
  const a=document.createElement("a");a.href="/agency#connections";a.textContent="Open Your agency";box.appendChild(a);return box}
41253
41876
  function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES.map(()=>"pending")}).stages;
41254
41877
  const sandbox=typeOf(state,c.locationId)==="sandbox";
41878
+ // A template or hidden account opens only so its owner can lock or unlock it and change its
41879
+ // type: nothing on its card builds into it (review L-1; a template can be the live snapshot source).
41880
+ const parkedType=["template","hidden"].includes(typeOf(state,c.locationId));
41255
41881
  const div=document.createElement("div");div.className="client";
41256
41882
  const head=document.createElement("div");head.className="client-head";
41257
41883
  const av=document.createElement("span");av.className="avatar lg";av.textContent=initials(c.name);head.appendChild(av);
@@ -41269,7 +41895,8 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41269
41895
  isel.onchange=async()=>{await postJSON("/api/industry",{locationId:c.locationId,industry:isel.value});load()};
41270
41896
  sels.appendChild(isel);head.appendChild(sels);div.appendChild(head);
41271
41897
  const costs=(state.costRecords||[]).filter(r=>r.locationId===c.locationId);if(costs.length){const details=document.createElement("details");const title=document.createElement("summary");title.textContent="Claude usage for this client";details.appendChild(title);for(const cost of costs){const p=document.createElement("p");p.textContent=costLabel(cost.kind)+": "+(typeof cost.costUsd==="number"?"$"+cost.costUsd.toFixed(4):"cost not reported")+". "+new Date(cost.at).toLocaleString();details.appendChild(p)}div.appendChild(details)}
41272
- if(!st.some(s=>s==="done"))div.appendChild(preflightCard(c));
41898
+ if(parkedType){const pn=document.createElement("p");pn.className="muted";pn.textContent="This is a "+typeOf(state,c.locationId)+" account, so nothing is built into it from here. You can lock or unlock it below, or change its type to Client to build it.";div.appendChild(pn)}
41899
+ else if(!st.some(s=>s==="done"))div.appendChild(preflightCard(c));
41273
41900
  const approveBuilding=async()=>{const typed=prompt('Approve the Blueprint Review for "'+c.name+'" and let Command OS build in this account.'+String.fromCharCode(10,10)+'Type the client name exactly to confirm:');if(typed===null)return;
41274
41901
  try{const response=await fetch("/api/build-approval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,confirmName:typed})});const r=await response.json();
41275
41902
  if(!response.ok||r.ok!==true){r.ok=false;r.error=r.error||"Command OS could not confirm the result. Refresh this client to read its saved state before retrying."}
@@ -41315,8 +41942,8 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41315
41942
  el.appendChild(note)}
41316
41943
  if(POWERED.includes(i)&&(st[i]||"pending")!=="done"){el.style.cursor="default";
41317
41944
  el.title="Command OS runs and checks this step. Its result comes from a run, never from a click."}
41318
- if(gate&&!anyBusy){const action=document.createElement("button");action.className="intakebtn";action.textContent=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};el.appendChild(action)}
41319
- const canBuild=i<=1||sandbox||Boolean((state.buildApproved||{})[c.locationId]);
41945
+ if(gate&&!anyBusy&&!parkedType){const action=document.createElement("button");action.className="intakebtn";action.textContent=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};el.appendChild(action)}
41946
+ const canBuild=!parkedType&&(i<=1||sandbox||Boolean((state.buildApproved||{})[c.locationId]));
41320
41947
  if(canBuild&&POWERED.includes(i)){const b=document.createElement("button");b.className="runbtn";
41321
41948
  const busy=run&&run.running;b.textContent=busy&&run.locationId===c.locationId&&run.stage===i?"Running\u2026":"Run this step";
41322
41949
  b.disabled=Boolean(busy)||(!st.some(s=>s==="done")&&!preflights[c.locationId]?.ok);
@@ -41337,7 +41964,7 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41337
41964
  for(const stale of view.stale){const p=document.createElement("p");p.textContent=LABELS[stale.stage]+": stale. "+stale.reason;div.appendChild(p)}
41338
41965
  if(view.approvedAt){const p=document.createElement("p");p.textContent="Building approved on "+new Date(view.approvedAt).toLocaleString()+".";div.appendChild(p)}
41339
41966
  for(const repair of view.repairs){const p=document.createElement("p");p.textContent="Live workflow repair: "+repair.name+". "+repair.changes.join(" ");div.appendChild(p)}
41340
- if(view.planHash){const approve=document.createElement("button");approve.textContent="Approve building\u2026";approve.onclick=async()=>{const typed=sandbox?c.name:prompt("Approve building and the listed live workflow repairs for "+c.name+". Type the client name to confirm.");if(typed===null)return;if(await postJSON("/api/build-approval",{locationId:c.locationId,confirmName:typed,repairHashes:view.repairs.map(r=>r.hash)}))load()};div.appendChild(approve)}
41967
+ if(view.planHash&&!parkedType){const approve=document.createElement("button");approve.textContent="Approve building\u2026";approve.onclick=async()=>{const typed=sandbox?c.name:prompt("Approve building and the listed live workflow repairs for "+c.name+". Type the client name to confirm.");if(typed===null)return;if(await postJSON("/api/build-approval",{locationId:c.locationId,confirmName:typed,repairHashes:view.repairs.map(r=>r.hash)}))load()};div.appendChild(approve)}
41341
41968
  }else{const p=document.createElement("p");p.textContent="The executable plan is prepared when Build runs.";div.appendChild(p)}
41342
41969
  if(view?.handoff)div.appendChild(handoffPanel(c,view));
41343
41970
  const _prot=PROTECTED[c.locationId];
@@ -41354,6 +41981,7 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41354
41981
  const rv=document.createElement("button");rv.className="qdel";rv.style.marginLeft=".5rem";rv.textContent="revoke";
41355
41982
  rv.onclick=async()=>{await postJSON("/api/build-approval",{locationId:c.locationId,revoke:true});load()};
41356
41983
  ab.appendChild(s1);ab.appendChild(rv);ab.appendChild(protectBtn(c))}
41984
+ else if(parkedType){ab.appendChild(protectBtn(c))}
41357
41985
  else{const w=document.createElement("span");w.className="muted";w.textContent="Automated building is off for this client. ";
41358
41986
  const btn=document.createElement("button");btn.className="intakebtn";btn.style.padding=".25rem .55rem";btn.textContent="Approve building\u2026";
41359
41987
  btn.onclick=approveBuilding;
@@ -41734,10 +42362,12 @@ function render(state,run){if(state.roster){ROSTER.splice(0,ROSTER.length,...sta
41734
42362
  const parked=sorted.filter(c=>match(c)&&["template","hidden"].includes(typeOf(state,c.locationId)));
41735
42363
  if(!board.length&&!parked.length){const d=document.createElement("div");d.className="client";d.innerHTML="<h2>No matches</h2>";m.appendChild(d);return}
41736
42364
  /* One card open at a time. Default: the account being built, else the busiest one. */
41737
- let act=(activeId&&board.some(c=>c.locationId===activeId))?activeId:null;
42365
+ /* A template or hidden account opens too: its card carries the lock, and an owner must never be locked out of their own account (BUG-2026-09-24-07). */
42366
+ const openable=[...board,...parked];
42367
+ let act=(activeId&&openable.some(c=>c.locationId===activeId))?activeId:null;
41738
42368
  if(!act&&run&&run.running&&board.some(c=>c.locationId===run.locationId))act=run.locationId;
41739
42369
  if(!act&&board.length)act=board[0].locationId;
41740
- const actC=board.find(c=>c.locationId===act);
42370
+ const actC=openable.find(c=>c.locationId===act);
41741
42371
  if(run&&run.running&&actC&&run.locationId!==actC.locationId){
41742
42372
  const other=ROSTER.find(c=>c.locationId===run.locationId);
41743
42373
  if(other){const ban=document.createElement("div");ban.className="card";ban.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.55rem .9rem;margin-bottom:1rem;border-color:var(--gold)";
@@ -41755,11 +42385,12 @@ function render(state,run){if(state.roster){ROSTER.splice(0,ROSTER.length,...sta
41755
42385
  const hint=document.createElement("p");hint.className="switchnote";
41756
42386
  hint.textContent="One account at a time. Opening another puts this one back in the list. Nothing is lost.";
41757
42387
  m.appendChild(hint)}
41758
- if(parked.length){const gh=document.createElement("div");gh.className="grouphead";
41759
- gh.textContent=(showParked?"\u25BE":"\u25B8")+" Templates & hidden ("+parked.length+")";
42388
+ const parkedRest=parked.filter(c=>c.locationId!==act);
42389
+ if(parkedRest.length){const gh=document.createElement("div");gh.className="grouphead";
42390
+ gh.textContent=(showParked?"\u25BE":"\u25B8")+" Templates & hidden ("+parkedRest.length+")";
41760
42391
  gh.onclick=()=>{showParked=!showParked;render(state,run)};m.appendChild(gh);
41761
42392
  if(showParked){const list2=document.createElement("div");list2.className="card acct-list";
41762
- for(const c of parked)list2.appendChild(acctRow(c,state,run));m.appendChild(list2)}}}
42393
+ for(const c of parkedRest)list2.appendChild(acctRow(c,state,run));m.appendChild(list2)}}}
41763
42394
  ${COCKPIT_TYPE_JS}
41764
42395
  load();
41765
42396
  </script></body></html>`;
@@ -42301,8 +42932,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42301
42932
  if (!bytes.length) throw new Error("that file is empty");
42302
42933
  if (bytes.length > DOC_MAX_BYTES) throw new Error("that file is over 20 MB");
42303
42934
  const dir = clientDocsDir(args.locationId);
42304
- fs34.mkdirSync(dir, { recursive: true, mode: 448 });
42305
- if (!insideDir(path32.join(appDataDir(), "clients"), dir)) throw new Error("This client's documents folder is not where it should be, so nothing was saved.");
42935
+ fs35.mkdirSync(dir, { recursive: true, mode: 448 });
42936
+ if (!insideDir(path33.join(appDataDir(), "clients"), dir)) throw new Error("This client's documents folder is not where it should be, so nothing was saved.");
42306
42937
  const saved = writeDocAtomically(dir, name, bytes);
42307
42938
  res.end(JSON.stringify({ ok: true, name: saved, files: listClientDocs(args.locationId) }));
42308
42939
  } catch (e) {
@@ -42477,6 +43108,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42477
43108
  try {
42478
43109
  const { locationId: locationId2, stage, intakeConfirmed } = JSON.parse(body);
42479
43110
  if (activeRun2?.running) throw new Error("a run is already in progress");
43111
+ const runType = readCockpitState().accounts?.[locationId2]?.type;
43112
+ if (runType === "template" || runType === "hidden") throw new Error(PARKED_NOT_BUILT);
42480
43113
  let st0 = readCockpitState();
42481
43114
  let inputHash;
42482
43115
  let normalizedAnswers;
@@ -42505,8 +43138,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42505
43138
  const snap = await readIntakeSnapshot(c, locationId2);
42506
43139
  if (snap.status === "unavailable") throw new Error(INTAKE_UNREADABLE);
42507
43140
  if (snap.status !== "received") throw new Error("Open the real form, submit it, then refresh.");
42508
- const required = composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }).filter((q2) => q2.required && !String(normalizedAnswers?.[q2.label] ?? normalizedAnswers?.[q2.key] ?? "").trim());
42509
- if (required.length) throw new Error("Complete these required intake answers: " + required.map((q2) => q2.label).join(", ") + ".");
43141
+ const required = composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }).filter((q3) => q3.required && !String(normalizedAnswers?.[q3.label] ?? normalizedAnswers?.[q3.key] ?? "").trim());
43142
+ if (required.length) throw new Error("Complete these required intake answers: " + required.map((q3) => q3.label).join(", ") + ".");
42510
43143
  }
42511
43144
  if (stage === 0 && intakeConfirmed !== true) throw new Error("Confirm the custom fields, form and published notification workflow on this client\u2019s Intake button.");
42512
43145
  if (!st0.accounts?.[locationId2]?.industry) throw new Error("Choose the client industry before running Intake.");
@@ -42552,7 +43185,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42552
43185
  industries: INDUSTRY_PACKS.map((p) => ({ slug: p.slug, label: p.label })),
42553
43186
  overlay: agency,
42554
43187
  warnings: unknownKeys(agency, INTAKE_QUESTIONS).map((k) => `Your overlay references "${k}", which isn't in the base intake.`),
42555
- questions: composed.map((q2) => ({ key: q2.key, label: q2.label, sectionLabel: q2.sectionLabel, required: q2.required, base: INTAKE_QUESTIONS.some((b) => b.key === q2.key) }))
43188
+ questions: composed.map((q3) => ({ key: q3.key, label: q3.label, sectionLabel: q3.sectionLabel, required: q3.required, base: INTAKE_QUESTIONS.some((b) => b.key === q3.key) }))
42556
43189
  }));
42557
43190
  return;
42558
43191
  }
@@ -42588,14 +43221,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42588
43221
  const snap = c ? await readIntakeSnapshot(c, locationId2, Boolean(st.intakeSent?.[locationId2])) : { status: "not_installed", fields: {}, queryKeys: {}, submissions: 0 };
42589
43222
  if (snap.status === "unavailable") throw new Error(INTAKE_UNREADABLE);
42590
43223
  const industry = st.accounts?.[locationId2]?.industry;
42591
- const questions = composeQuestionSet({ industry, agency: readOverlay() }).map((q2) => ({ key: q2.key, contactTag: q2.field.kind === "standard" ? q2.field.tag : void 0, label: q2.label, sectionLabel: q2.sectionLabel, required: q2.required, options: q2.options ?? null, placeholder: q2.placeholder ?? "", multi: q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS" }));
43224
+ const questions = composeQuestionSet({ industry, agency: readOverlay() }).map((q3) => ({ key: q3.key, contactTag: q3.field.kind === "standard" ? q3.field.tag : void 0, label: q3.label, sectionLabel: q3.sectionLabel, required: q3.required, options: q3.options ?? null, placeholder: q3.placeholder ?? "", multi: q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS" }));
42592
43225
  const answers = normalizedIntake(snap, st.intakeDrafts?.[locationId2]);
42593
43226
  const submittedAnswers = normalizedIntake(snap);
42594
43227
  const hash = intakeInput(submittedAnswers, industry, composeQuestionSet({ industry, agency: readOverlay() }));
42595
43228
  const latestState = readCockpitState();
42596
43229
  writeCockpitState({ ...latestState, intakeInputs: { ...latestState.intakeInputs, [locationId2]: { hash, answers: submittedAnswers, status: snap.status } } });
42597
43230
  res.writeHead(200, { "Content-Type": "application/json" });
42598
- res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q2) => q2.required && !String(answers[q2.label] ?? answers[q2.key] ?? "").trim()).map((q2) => q2.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
43231
+ res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q3) => q3.required && !String(answers[q3.label] ?? answers[q3.key] ?? "").trim()).map((q3) => q3.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
42599
43232
  } catch (e) {
42600
43233
  res.writeHead(400, { "Content-Type": "application/json" });
42601
43234
  logQuietly(`client-intake ${new URL(req.url || "/", "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
@@ -42614,7 +43247,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42614
43247
  const snap = await readIntakeSnapshot(c, args.locationId, false);
42615
43248
  if (!snap.formId || !snap.formUrl) throw new Error(INTAKE_FORM_REQUIRED);
42616
43249
  res.writeHead(200, { "Content-Type": "application/json" });
42617
- const identityLabels = new Set(composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).filter((q2) => q2.field.kind === "standard").map((q2) => q2.label));
43250
+ const identityLabels = new Set(composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).filter((q3) => q3.field.kind === "standard").map((q3) => q3.label));
42618
43251
  const customAnswers = Object.fromEntries(Object.entries(args.answers ?? {}).filter(([label]) => !identityLabels.has(label)));
42619
43252
  const pf = prefillFormUrl(snap.formUrl, snap.queryKeys, customAnswers, args.contact ?? {}, snap.altQueryKeys ?? {});
42620
43253
  res.end(JSON.stringify({ ok: true, url: pf.url, leftovers: pf.leftovers }));
@@ -42656,28 +43289,28 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42656
43289
  if (args.file) {
42657
43290
  const name = safeDocName(args.file);
42658
43291
  const dir = clientDocsDir(args.locationId);
42659
- const p = path32.join(dir, name);
42660
- if (!fs34.existsSync(p)) throw new Error("that document is not saved for this client");
42661
- if (fs34.lstatSync(p).isSymbolicLink() || !insideDir(dir, p)) throw new Error("that document is not saved for this client");
42662
- const text2 = fs34.readFileSync(p, "utf8");
43292
+ const p = path33.join(dir, name);
43293
+ if (!fs35.existsSync(p)) throw new Error("that document is not saved for this client");
43294
+ if (fs35.lstatSync(p).isSymbolicLink() || !insideDir(dir, p)) throw new Error("that document is not saved for this client");
43295
+ const text2 = fs35.readFileSync(p, "utf8");
42663
43296
  if (text2.length > 6e4) throw new Error("The limit is 60,000 characters. Paste an excerpt or shorten this text file.");
42664
43297
  fileSource = { kind: "text", text: text2 };
42665
43298
  }
42666
43299
  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 ?? "") };
42667
43300
  let recorderPatterns = [];
42668
43301
  if (source.kind === "recorder") {
42669
- recorderPatterns = await new Promise((resolve12) => {
43302
+ recorderPatterns = await new Promise((resolve13) => {
42670
43303
  const mcpList = claudeCommand(claudeBin(), ["mcp", "list"]);
42671
43304
  const ls = (0, import_child_process5.spawn)(mcpList.command, mcpList.args, { stdio: ["ignore", "pipe", "ignore"] });
42672
43305
  let buf = "";
42673
43306
  ls.stdout.on("data", (d) => {
42674
43307
  buf += d.toString();
42675
43308
  });
42676
- ls.on("close", () => resolve12(recorderAllowPatterns(buf)));
42677
- ls.on("error", () => resolve12([]));
43309
+ ls.on("close", () => resolve13(recorderAllowPatterns(buf)));
43310
+ ls.on("error", () => resolve13([]));
42678
43311
  setTimeout(() => {
42679
43312
  ls.kill("SIGKILL");
42680
- resolve12(recorderAllowPatterns(buf));
43313
+ resolve13(recorderAllowPatterns(buf));
42681
43314
  }, 9e4).unref?.();
42682
43315
  });
42683
43316
  if (!recorderPatterns.length) {
@@ -42688,10 +43321,10 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42688
43321
  }
42689
43322
  const out = await prefillFromSource(
42690
43323
  (prompt, tools) => callClaudeRecorded(bin, ["-p", prompt, "--max-turns", "25", "--disallowedTools", tools.deny, "--allowedTools", tools.allow || ""], { locationId: args.locationId, stage: 0, kind: "intake extraction" }),
42691
- composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q2) => q2.label),
43324
+ composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q3) => q3.label),
42692
43325
  source,
42693
43326
  recorderPatterns,
42694
- composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q2) => ({ label: q2.label, options: q2.options, multi: q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS", dataType: q2.field.kind === "custom" ? q2.field.dataType : q2.field.tag }))
43327
+ composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q3) => ({ label: q3.label, options: q3.options, multi: q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS", dataType: q3.field.kind === "custom" ? q3.field.dataType : q3.field.tag }))
42695
43328
  );
42696
43329
  res.writeHead(200, { "Content-Type": "application/json" });
42697
43330
  res.end(JSON.stringify({ ...out, costs: readRunCosts().filter((r) => r.locationId === args.locationId).slice(-2) }));
@@ -42733,14 +43366,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42733
43366
  }
42734
43367
  if (req.method === "GET" && /^\/brand\/[a-z0-9-]+\/screenshot\.png$/.test(url)) {
42735
43368
  const slug3 = url.split("/")[2];
42736
- const file2 = path32.join(brandDir(slug3), "screenshot.png");
42737
- if (!fs34.existsSync(file2)) {
43369
+ const file2 = path33.join(brandDir(slug3), "screenshot.png");
43370
+ if (!fs35.existsSync(file2)) {
42738
43371
  res.writeHead(404);
42739
43372
  res.end();
42740
43373
  return;
42741
43374
  }
42742
43375
  res.writeHead(200, { "Content-Type": "image/png", "Cache-Control": "no-store" });
42743
- res.end(fs34.readFileSync(file2));
43376
+ res.end(fs35.readFileSync(file2));
42744
43377
  return;
42745
43378
  }
42746
43379
  if (req.method === "GET" && /^\/brand\/[a-z0-9-]+$/.test(url)) {
@@ -42749,7 +43382,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42749
43382
  const dna = houseLooks().find((d) => d.slug === slug3) ?? readBrand(slug3);
42750
43383
  const dir = brandExists(slug3) ? brandDir(slug3) : void 0;
42751
43384
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
42752
- res.end(brandCardHtml(dna, { screenshot: dir ? fs34.existsSync(path32.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
43385
+ res.end(brandCardHtml(dna, { screenshot: dir ? fs35.existsSync(path33.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
42753
43386
  } catch (e) {
42754
43387
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
42755
43388
  res.end(e.message);
@@ -42911,7 +43544,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
42911
43544
  }
42912
43545
  try {
42913
43546
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
42914
- res.end(fs34.readFileSync(localCopyPath(locationId2), "utf8"));
43547
+ res.end(fs35.readFileSync(localCopyPath(locationId2), "utf8"));
42915
43548
  } catch {
42916
43549
  res.end("The handoff is not prepared yet. Run Verify first.");
42917
43550
  }
@@ -43011,8 +43644,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
43011
43644
  return;
43012
43645
  }
43013
43646
  if (req.method === "GET" && url === "/blueprint") {
43014
- const q2 = new URL(req.url || "/", "http://x").searchParams;
43015
- const locationId2 = q2.get("locationId") || "";
43647
+ const q3 = new URL(req.url || "/", "http://x").searchParams;
43648
+ const locationId2 = q3.get("locationId") || "";
43016
43649
  const st = readCockpitState();
43017
43650
  const review = st.reviews?.[locationId2];
43018
43651
  if (!review?.ok) {
@@ -43022,7 +43655,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
43022
43655
  }
43023
43656
  const name = currentRoster().find((r) => r.locationId === locationId2)?.name || locationId2;
43024
43657
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
43025
- res.end(reviewDocumentHtml(review, { scope: documentScopeFor(st, locationId2), assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: assessmentPurchasesFor(st, locationId2), planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q2.get("agency") || st.agencyName, accent: q2.get("accent") || st.agencyAccent }));
43658
+ res.end(reviewDocumentHtml(review, { scope: documentScopeFor(st, locationId2), assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: assessmentPurchasesFor(st, locationId2), planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q3.get("agency") || st.agencyName, accent: q3.get("accent") || st.agencyAccent }));
43026
43659
  return;
43027
43660
  }
43028
43661
  if (req.method === "GET" && url === "/api/review") {
@@ -43483,9 +44116,9 @@ ${opportunityNoteFailure?.detail ?? ""}`);
43483
44116
  res.end();
43484
44117
  }));
43485
44118
  try {
43486
- await new Promise((resolve12, reject) => {
44119
+ await new Promise((resolve13, reject) => {
43487
44120
  server2.once("error", reject);
43488
- server2.listen(port, "127.0.0.1", resolve12);
44121
+ server2.listen(port, "127.0.0.1", resolve13);
43489
44122
  });
43490
44123
  } catch (e) {
43491
44124
  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)}`;
@@ -43511,13 +44144,13 @@ ${opportunityNoteFailure?.detail ?? ""}`);
43511
44144
  if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
43512
44145
  `);
43513
44146
  });
43514
- return await new Promise((resolve12) => {
43515
- const stop = () => server2.close(() => resolve12(0));
43516
- process.on("SIGINT", stop);
43517
- process.on("SIGTERM", stop);
44147
+ return await new Promise((resolve13) => {
44148
+ const stop2 = () => server2.close(() => resolve13(0));
44149
+ process.on("SIGINT", stop2);
44150
+ process.on("SIGTERM", stop2);
43518
44151
  });
43519
44152
  }
43520
- var import_zod62, import_node_util4, fs34, path32, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, ConnectionRequest, DisconnectRequest;
44153
+ var import_zod62, import_node_util4, fs35, path33, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, ConnectionRequest, DisconnectRequest;
43521
44154
  var init_dashboard = __esm({
43522
44155
  "src/dashboard.ts"() {
43523
44156
  "use strict";
@@ -43545,8 +44178,8 @@ var init_dashboard = __esm({
43545
44178
  init_handoff_snapshot();
43546
44179
  init_member_progress();
43547
44180
  init_build_obligations();
43548
- fs34 = __toESM(require("fs"));
43549
- path32 = __toESM(require("path"));
44181
+ fs35 = __toESM(require("fs"));
44182
+ path33 = __toESM(require("path"));
43550
44183
  http = __toESM(require("http"));
43551
44184
  import_child_process4 = require("child_process");
43552
44185
  init_credentials_store();
@@ -43624,6 +44257,7 @@ var init_dashboard = __esm({
43624
44257
  UPGRADE_MSG = "\n The cockpit is not included in this licence, so it did not start.\n Nothing was changed.\n\n";
43625
44258
  DOC_EXTS = /* @__PURE__ */ new Set([".md", ".txt", ".vtt", ".srt", ".csv"]);
43626
44259
  DOC_MAX_BYTES = 20 * 1024 * 1024;
44260
+ PARKED_NOT_BUILT = "Template and hidden accounts are not built from Command OS. Change the account's type to Client first.";
43627
44261
  NEEDS_AGENCY_KEY = "No agency key on file. Run register_agency_key in GHL Command first.";
43628
44262
  INTAKE_UNREADABLE = "Command OS could not read this client's account. Paste its key again in Build a Client Account.";
43629
44263
  INTAKE_FORM_REQUIRED = "Run the Intake stage in Client Builds to create the form, then come back and submit.";
@@ -44115,12 +44749,12 @@ ${companies.map((c) => ` ${c.name || c.companyId} (${c.companyId})`).join("\n")
44115
44749
  ]
44116
44750
  };
44117
44751
  }
44118
- const tokens = registry2.listLocations().map((loc) => ({
44752
+ const tokens2 = registry2.listLocations().map((loc) => ({
44119
44753
  ...loc,
44120
44754
  companyId: registry2.getToken(loc.locationId)?.companyId
44121
44755
  }));
44122
44756
  const currentId = client.defaultLocationId;
44123
- const lines = tokens.map((loc) => {
44757
+ const lines = tokens2.map((loc) => {
44124
44758
  const marker = loc.locationId === currentId ? "\u2192 " : " ";
44125
44759
  const company = loc.companyId ? ` [company ${loc.companyId}${companiesWithFirebase.has(loc.companyId) ? ", Firebase \u2713" : ", no Firebase"}]` : "";
44126
44760
  return `${marker}${loc.name} (${loc.locationId})${company}`;
@@ -44133,7 +44767,7 @@ ${companies.map((c) => ` ${c.name || c.companyId} (${c.companyId})`).join("\n")
44133
44767
  content: [
44134
44768
  {
44135
44769
  type: "text",
44136
- text: `Token Registry (${tokens.length} locations):
44770
+ text: `Token Registry (${tokens2.length} locations):
44137
44771
  ${lines.join("\n")}
44138
44772
 
44139
44773
  \u2192 = currently active${fbSection}`
@@ -44288,7 +44922,7 @@ var init_location_switcher = __esm({
44288
44922
 
44289
44923
  // src/tools/bulk-operations.ts
44290
44924
  function delay2(ms) {
44291
- return new Promise((resolve12) => setTimeout(resolve12, ms));
44925
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
44292
44926
  }
44293
44927
  function formatResults(op, results, total) {
44294
44928
  return `${op}: ${results.success} success, ${results.failed} failed out of ${total}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
@@ -44533,7 +45167,7 @@ var init_asset_ownership = __esm({
44533
45167
  function sectionItems(section2) {
44534
45168
  return Array.isArray(section2) ? section2 : [];
44535
45169
  }
44536
- function bullet(lines) {
45170
+ function bullet2(lines) {
44537
45171
  return lines.length ? lines.map((l) => `- ${l}`).join("\n") : "- None found.";
44538
45172
  }
44539
45173
  function plainReason(reason3) {
@@ -44555,7 +45189,7 @@ function sectionList(section2, render) {
44555
45189
  if (!Array.isArray(section2)) {
44556
45190
  return `**Not read.** ${plainReason(section2.unavailable)}. This section is unknown, which is NOT the same as empty. Check it by hand before you rely on this document.`;
44557
45191
  }
44558
- return bullet(section2.map(render));
45192
+ return bullet2(section2.map(render));
44559
45193
  }
44560
45194
  function buildHandoffDoc(input) {
44561
45195
  const {
@@ -44577,13 +45211,13 @@ function buildHandoffDoc(input) {
44577
45211
  if (!items.length) return "";
44578
45212
  return `**${kind}** ${OWNER_MEANING[kind]}
44579
45213
 
44580
- ${bullet(
45214
+ ${bullet2(
44581
45215
  items.map((i) => i.note ? `${i.asset} (${i.note})` : i.asset)
44582
45216
  )}`;
44583
45217
  }).filter(Boolean).join("\n\n") : "Nothing has been recorded yet. Every asset below is unanswered.";
44584
45218
  const unknownSection = ownership.unknown.length ? `These exist in the account but nobody has said who owns them. Answer them once and they stay answered:
44585
45219
 
44586
- ${bullet(ownership.unknown)}` : "Every asset found in this account has a recorded owner.";
45220
+ ${bullet2(ownership.unknown)}` : "Every asset found in this account has a recorded owner.";
44587
45221
  const webhookSection = sectionList(webhooks, (w) => `${w.name} sends to ${w.url}${w.events.length ? ` when: ${w.events.join(", ")}` : ""}`) + (webhookScanPartial ? `
44588
45222
 
44589
45223
  **This list may be incomplete.** ${webhookScanPartial}` : "");
@@ -44660,7 +45294,7 @@ ${ownership.unreadable.length ? `
44660
45294
 
44661
45295
  Somebody edited these by hand and the answer no longer parses. They need a human:
44662
45296
 
44663
- ${bullet(ownership.unreadable)}
45297
+ ${bullet2(ownership.unreadable)}
44664
45298
  ` : ""}
44665
45299
  ## Rotate these on the way out
44666
45300
 
@@ -44693,7 +45327,7 @@ var init_handoff_doc = __esm({
44693
45327
 
44694
45328
  // src/tools/account-export.ts
44695
45329
  function delay3(ms) {
44696
- return new Promise((resolve12) => setTimeout(resolve12, ms));
45330
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
44697
45331
  }
44698
45332
  function builderSectionAvailability(builderLocationId, requestedLocationId) {
44699
45333
  if (!builderLocationId) {
@@ -45408,13 +46042,13 @@ function contactServiceNote(payload) {
45408
46042
  function registerSmartListTools(server2, builderClient) {
45409
46043
  if (!builderClient) return;
45410
46044
  const client = builderClient;
45411
- async function smartListRequest(method, path41, body) {
45412
- const url = `${SMARTLIST_BASE}${path41}`;
46045
+ async function smartListRequest(method, path42, body) {
46046
+ const url = `${SMARTLIST_BASE}${path42}`;
45413
46047
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
45414
46048
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
45415
46049
  if (!response.ok) {
45416
46050
  const text3 = await response.text();
45417
- throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path41}
46051
+ throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path42}
45418
46052
  ${text3}`);
45419
46053
  }
45420
46054
  const text2 = await response.text();
@@ -45659,11 +46293,11 @@ var init_smart_lists = __esm({
45659
46293
  function registerReputationTools(server2, builderClient) {
45660
46294
  if (!builderClient) return;
45661
46295
  const client = builderClient;
45662
- async function reputationRequest(method, path41) {
45663
- const response = await builderRequest(client, method, `${REPUTATION_BASE}${path41}`);
46296
+ async function reputationRequest(method, path42) {
46297
+ const response = await builderRequest(client, method, `${REPUTATION_BASE}${path42}`);
45664
46298
  if (!response.ok) {
45665
46299
  const text3 = await response.text();
45666
- throw new Error(`Reputation API Error ${response.status}: ${method} ${path41}
46300
+ throw new Error(`Reputation API Error ${response.status}: ${method} ${path42}
45667
46301
  ${text3}`);
45668
46302
  }
45669
46303
  const text2 = await response.text();
@@ -45705,7 +46339,7 @@ ${text3}`);
45705
46339
  );
45706
46340
  }
45707
46341
  function buildReviewsQuery(locationId2, opts = {}) {
45708
- const q2 = [
46342
+ const q3 = [
45709
46343
  `filterParams[locationId][0][value]=${locationId2}`,
45710
46344
  `filterParams[locationId][0][condition]=eq`,
45711
46345
  `filterParams[deleted][0][value]=${opts.includeDeleted ? "true" : "false"}`,
@@ -45719,13 +46353,13 @@ function buildReviewsQuery(locationId2, opts = {}) {
45719
46353
  if (opts.maxRating !== void 0) bounds.push([opts.maxRating, "lte"]);
45720
46354
  }
45721
46355
  bounds.forEach(([value, condition], i) => {
45722
- q2.push(`filterParams[starRating][${i}][value]=${value}`);
45723
- q2.push(`filterParams[starRating][${i}][condition]=${condition}`);
46356
+ q3.push(`filterParams[starRating][${i}][value]=${value}`);
46357
+ q3.push(`filterParams[starRating][${i}][condition]=${condition}`);
45724
46358
  });
45725
- q2.push(`sortParams[dateAdded]=-1`);
45726
- q2.push(`pageNumber=${opts.pageNumber ?? 1}`);
45727
- q2.push(`pageSize=${opts.pageSize ?? 10}`);
45728
- const query = q2.map((p) => {
46359
+ q3.push(`sortParams[dateAdded]=-1`);
46360
+ q3.push(`pageNumber=${opts.pageNumber ?? 1}`);
46361
+ q3.push(`pageSize=${opts.pageSize ?? 10}`);
46362
+ const query = q3.map((p) => {
45729
46363
  const i = p.indexOf("=");
45730
46364
  return `${encodeURIComponent(p.slice(0, i))}=${encodeURIComponent(p.slice(i + 1))}`;
45731
46365
  }).join("&");
@@ -45801,11 +46435,11 @@ var init_email_campaigns = __esm({
45801
46435
  function registerMembershipTools(server2, builderClient) {
45802
46436
  if (!builderClient) return;
45803
46437
  const client = builderClient;
45804
- async function membershipRequest(path41, method = "GET", body) {
45805
- const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path41}`, body ? { body } : {});
46438
+ async function membershipRequest(path42, method = "GET", body) {
46439
+ const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path42}`, body ? { body } : {});
45806
46440
  if (!response.ok) {
45807
46441
  const text3 = await response.text();
45808
- throw new Error(`Membership API Error ${response.status}: ${method} ${path41}
46442
+ throw new Error(`Membership API Error ${response.status}: ${method} ${path42}
45809
46443
  ${text3}`);
45810
46444
  }
45811
46445
  const text2 = await response.text();
@@ -45991,28 +46625,28 @@ var init_memberships = __esm({
45991
46625
 
45992
46626
  // src/tools/template-deployer.ts
45993
46627
  function delay4(ms) {
45994
- return new Promise((resolve12) => setTimeout(resolve12, ms));
46628
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
45995
46629
  }
45996
46630
  function isAlreadyExistsError(message) {
45997
46631
  return /already\s+exists?\b/i.test(message) || /duplicate/i.test(message);
45998
46632
  }
45999
46633
  function registerTemplateDeployerTools(server2, client) {
46000
46634
  const builderClient = WorkflowBuilderClient.fromEnv();
46001
- const templatesDir = path33.resolve(__dirname, "..", "templates");
46635
+ const templatesDir = path34.resolve(__dirname, "..", "templates");
46002
46636
  function validateTemplatePath(templateFile) {
46003
- const resolved2 = path33.resolve(templateFile);
46004
- const realPath = fs35.existsSync(resolved2) ? fs35.realpathSync(resolved2) : resolved2;
46637
+ const resolved2 = path34.resolve(templateFile);
46638
+ const realPath = fs36.existsSync(resolved2) ? fs36.realpathSync(resolved2) : resolved2;
46005
46639
  const allowedDirs = [
46006
46640
  templatesDir,
46007
- path33.resolve(process.cwd(), "templates"),
46008
- path33.resolve(__dirname, "..", "..", "templates")
46009
- ].map((d) => fs35.existsSync(d) ? fs35.realpathSync(d) : d);
46010
- const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path33.sep));
46641
+ path34.resolve(process.cwd(), "templates"),
46642
+ path34.resolve(__dirname, "..", "..", "templates")
46643
+ ].map((d) => fs36.existsSync(d) ? fs36.realpathSync(d) : d);
46644
+ const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path34.sep));
46011
46645
  if (!isAllowed) {
46012
46646
  throw new Error(`Template file must be inside a templates/ directory. Got: ${realPath}`);
46013
46647
  }
46014
- if (path33.relative(allowedDirs[0], realPath).startsWith("..")) {
46015
- const otherMatch = allowedDirs.slice(1).some((d) => !path33.relative(d, realPath).startsWith(".."));
46648
+ if (path34.relative(allowedDirs[0], realPath).startsWith("..")) {
46649
+ const otherMatch = allowedDirs.slice(1).some((d) => !path34.relative(d, realPath).startsWith(".."));
46016
46650
  if (!otherMatch) {
46017
46651
  throw new Error(`Template path escapes allowed directory.`);
46018
46652
  }
@@ -46027,26 +46661,26 @@ function registerTemplateDeployerTools(server2, client) {
46027
46661
  try {
46028
46662
  const dirs = [
46029
46663
  templatesDir,
46030
- path33.resolve(process.cwd(), "templates"),
46031
- path33.resolve(__dirname, "..", "..", "templates")
46664
+ path34.resolve(process.cwd(), "templates"),
46665
+ path34.resolve(__dirname, "..", "..", "templates")
46032
46666
  ];
46033
46667
  const templates = [];
46034
46668
  const seen = /* @__PURE__ */ new Set();
46035
46669
  for (const dir of dirs) {
46036
- if (!fs35.existsSync(dir)) continue;
46037
- const files = fs35.readdirSync(dir).filter((f) => f.endsWith(".json"));
46670
+ if (!fs36.existsSync(dir)) continue;
46671
+ const files = fs36.readdirSync(dir).filter((f) => f.endsWith(".json"));
46038
46672
  for (const file2 of files) {
46039
- const full = path33.join(dir, file2);
46673
+ const full = path34.join(dir, file2);
46040
46674
  let real;
46041
46675
  try {
46042
- real = fs35.realpathSync(full);
46676
+ real = fs36.realpathSync(full);
46043
46677
  } catch {
46044
46678
  continue;
46045
46679
  }
46046
46680
  if (seen.has(real)) continue;
46047
46681
  seen.add(real);
46048
46682
  try {
46049
- const content = JSON.parse(fs35.readFileSync(full, "utf-8"));
46683
+ const content = JSON.parse(fs36.readFileSync(full, "utf-8"));
46050
46684
  if (typeof content.templateName !== "string" || !content.templateName) continue;
46051
46685
  const parsed2 = TemplateSchema.safeParse(content);
46052
46686
  templates.push({
@@ -46083,7 +46717,7 @@ function registerTemplateDeployerTools(server2, client) {
46083
46717
  async ({ templateFile }) => {
46084
46718
  try {
46085
46719
  const safePath = validateTemplatePath(templateFile);
46086
- const content = JSON.parse(fs35.readFileSync(safePath, "utf-8"));
46720
+ const content = JSON.parse(fs36.readFileSync(safePath, "utf-8"));
46087
46721
  return {
46088
46722
  content: [
46089
46723
  {
@@ -46123,8 +46757,8 @@ function registerTemplateDeployerTools(server2, client) {
46123
46757
  try {
46124
46758
  const locId = client.resolveLocationId(locationId2);
46125
46759
  const safePath = validateTemplatePath(templateFile);
46126
- const template = TemplateSchema.parse(JSON.parse(fs35.readFileSync(safePath, "utf-8")));
46127
- const resolve12 = (text2) => {
46760
+ const template = TemplateSchema.parse(JSON.parse(fs36.readFileSync(safePath, "utf-8")));
46761
+ const resolve13 = (text2) => {
46128
46762
  if (typeof text2 !== "string") return text2;
46129
46763
  let result = text2;
46130
46764
  for (const [key, value] of Object.entries(answers)) {
@@ -46137,7 +46771,7 @@ function registerTemplateDeployerTools(server2, client) {
46137
46771
  return result;
46138
46772
  };
46139
46773
  const resolveObj = (obj) => {
46140
- if (typeof obj === "string") return resolve12(obj);
46774
+ if (typeof obj === "string") return resolve13(obj);
46141
46775
  if (Array.isArray(obj)) return obj.map(resolveObj);
46142
46776
  if (obj && typeof obj === "object") {
46143
46777
  const result = {};
@@ -46495,15 +47129,15 @@ ${errors.join("\n")}` : dryRun ? "\nNo errors!" : "\nNo errors \u2014 every item
46495
47129
  }
46496
47130
  );
46497
47131
  }
46498
- var import_zod71, fs35, path33, TemplateSchema;
47132
+ var import_zod71, fs36, path34, TemplateSchema;
46499
47133
  var init_template_deployer = __esm({
46500
47134
  "src/tools/template-deployer.ts"() {
46501
47135
  "use strict";
46502
47136
  import_zod71 = require("zod");
46503
47137
  init_workflow_builder_client();
46504
47138
  init_tool_helpers();
46505
- fs35 = __toESM(require("fs"));
46506
- path33 = __toESM(require("path"));
47139
+ fs36 = __toESM(require("fs"));
47140
+ path34 = __toESM(require("path"));
46507
47141
  init_workflow_builder_client();
46508
47142
  TemplateSchema = import_zod71.z.object({
46509
47143
  templateName: import_zod71.z.string(),
@@ -46824,10 +47458,10 @@ async function confirm(run) {
46824
47458
  async function runProbes(client, loc) {
46825
47459
  return Promise.all(
46826
47460
  PROBES.map((probe) => confirm(async () => {
46827
- const path41 = probe.path.replace("{loc}", loc);
47461
+ const path42 = probe.path.replace("{loc}", loc);
46828
47462
  const params = probe.params ? Object.fromEntries(Object.entries(probe.params).map(([k, v]) => [k, substitute(v, loc)])) : void 0;
46829
47463
  try {
46830
- const raw = await client.get(path41, { params, noRetry: true });
47464
+ const raw = await client.get(path42, { params, noRetry: true });
46831
47465
  const count2 = countRows(raw, probe.listKeys);
46832
47466
  return {
46833
47467
  key: probe.key,
@@ -47103,14 +47737,14 @@ var init_checkup = __esm({
47103
47737
 
47104
47738
  // src/tools/user-guide.ts
47105
47739
  function resolveGuidePath() {
47106
- const bundled = path34.resolve(__dirname, "..", "guide", "guide.html");
47107
- if (fs36.existsSync(bundled)) return bundled;
47108
- const devRoot = path34.resolve(__dirname, "..", "..");
47740
+ const bundled = path35.resolve(__dirname, "..", "guide", "guide.html");
47741
+ if (fs37.existsSync(bundled)) return bundled;
47742
+ const devRoot = path35.resolve(__dirname, "..", "..");
47109
47743
  try {
47110
- const pkg2 = JSON.parse(fs36.readFileSync(path34.join(devRoot, "package.json"), "utf8"));
47744
+ const pkg2 = JSON.parse(fs37.readFileSync(path35.join(devRoot, "package.json"), "utf8"));
47111
47745
  if (pkg2.name === "@elitedcs/ghl-mcp") {
47112
- const dev = path34.join(devRoot, "guide", "guide.html");
47113
- if (fs36.existsSync(dev)) return dev;
47746
+ const dev = path35.join(devRoot, "guide", "guide.html");
47747
+ if (fs37.existsSync(dev)) return dev;
47114
47748
  }
47115
47749
  } catch {
47116
47750
  }
@@ -47119,7 +47753,7 @@ function resolveGuidePath() {
47119
47753
  function guideContents(file2) {
47120
47754
  let html2 = "";
47121
47755
  try {
47122
- html2 = fs36.readFileSync(file2, "utf8");
47756
+ html2 = fs37.readFileSync(file2, "utf8");
47123
47757
  } catch {
47124
47758
  return "The guide is installed but could not be read.";
47125
47759
  }
@@ -47143,7 +47777,7 @@ function registerUserGuideTools(server2) {
47143
47777
  { open: import_zod73.z.boolean().optional().describe("Open the guide in the browser. Takes over the person's screen \u2014 only when they asked for it.") },
47144
47778
  async ({ open }) => {
47145
47779
  const guidePath = resolveGuidePath();
47146
- if (!fs36.existsSync(guidePath)) {
47780
+ if (!fs37.existsSync(guidePath)) {
47147
47781
  return {
47148
47782
  content: [{
47149
47783
  type: "text",
@@ -47176,14 +47810,14 @@ ${guidePath}`;
47176
47810
  }
47177
47811
  );
47178
47812
  }
47179
- var import_child_process6, import_zod73, fs36, path34;
47813
+ var import_child_process6, import_zod73, fs37, path35;
47180
47814
  var init_user_guide = __esm({
47181
47815
  "src/tools/user-guide.ts"() {
47182
47816
  "use strict";
47183
47817
  import_child_process6 = require("child_process");
47184
47818
  import_zod73 = require("zod");
47185
- fs36 = __toESM(require("fs"));
47186
- path34 = __toESM(require("path"));
47819
+ fs37 = __toESM(require("fs"));
47820
+ path35 = __toESM(require("path"));
47187
47821
  }
47188
47822
  });
47189
47823
 
@@ -47244,7 +47878,8 @@ function registerAgencyProfileTools(server2) {
47244
47878
  defaults: import_zod74.z.object({
47245
47879
  timezone: import_zod74.z.string().optional(),
47246
47880
  industry: import_zod74.z.string().optional()
47247
- }).optional()
47881
+ }).optional(),
47882
+ clientFilesDir: import_zod74.z.string().optional().describe("A full folder path on this machine where client call files are also written, usually your Google Drive for desktop folder so Drive syncs them. A relative path is refused.")
47248
47883
  },
47249
47884
  async (args) => {
47250
47885
  const patch = Object.fromEntries(
@@ -47822,7 +48457,7 @@ function blockContrast(t, fallbackBg) {
47822
48457
  if (!fg || !bg) return null;
47823
48458
  return Math.round(contrastHex(fg, bg) * 100) / 100;
47824
48459
  }
47825
- function inside(inner, outer, inset = 0) {
48460
+ function inside2(inner, outer, inset = 0) {
47826
48461
  return inner.x >= outer.x + inset - 0.5 && inner.y >= outer.y + inset - 0.5 && inner.x + inner.w <= outer.x + outer.w - inset + 0.5 && inner.y + inner.h <= outer.y + outer.h - inset + 0.5;
47827
48462
  }
47828
48463
  function clusterBoxes(boxes, gap = 16) {
@@ -47917,7 +48552,7 @@ function checkSlides(probe, pixels, ctx, rendered) {
47917
48552
  const bad = [];
47918
48553
  for (const s of slides) {
47919
48554
  const inset = Math.round(s.box.w * 0.05);
47920
- const outside = [...s.textBlocks.filter((t) => t.words > 0).map((t) => ({ box: t.box, what: `"${t.text.slice(0, 20)}"` })), ...s.images.map((i) => ({ box: i.box, what: i.tag }))].filter((e) => !inside(e.box, s.box, inset));
48555
+ const outside = [...s.textBlocks.filter((t) => t.words > 0).map((t) => ({ box: t.box, what: `"${t.text.slice(0, 20)}"` })), ...s.images.map((i) => ({ box: i.box, what: i.tag }))].filter((e) => !inside2(e.box, s.box, inset));
47921
48556
  if (outside.length) bad.push(`${slideName(s)}: ${outside.slice(0, 2).map((o) => o.what).join(", ")} inside the ${inset} px band`);
47922
48557
  }
47923
48558
  out.push(graded(rule("S5"), bad.length === 0, bad.length ? fails3(bad) : `all content \u2265${Math.round(slides[0].box.w * 0.05)} px from every edge`));
@@ -48139,7 +48774,7 @@ function checkSlides(probe, pixels, ctx, rendered) {
48139
48774
  const l = logos[0];
48140
48775
  if (l) {
48141
48776
  if (l.box.h > 28) bad.push(`${slideName(s)} logo ${Math.round(l.box.h)} px tall`);
48142
- if (!inside(l.box, s.box, inset)) bad.push(`${slideName(s)} logo inside the safe band`);
48777
+ if (!inside2(l.box, s.box, inset)) bad.push(`${slideName(s)} logo inside the safe band`);
48143
48778
  } else if (byHook && ctx.logoSig) bad.push(`${slideName(s)} has a data-role="logo" element but not the stored logo's bytes (conformance)`);
48144
48779
  }
48145
48780
  out.push(graded(rule("S21"), bad.length === 0, bad.length ? fails3(bad) : "stored logo on every slide, \u226424 px, inside the safe zone", void 0, ctx.logoSig ? void 0 : 'No stored logo to match; only the data-role="logo" hook was available.'));
@@ -48699,20 +49334,20 @@ function checkConversion(p, ctx, rendered) {
48699
49334
  invented.push(`quote attributed to ${t.name} that they did not give`);
48700
49335
  }
48701
49336
  }
48702
- const digits2 = (x) => x.replace(/[,\s]/g, "");
49337
+ const digits = (x) => x.replace(/[,\s]/g, "");
48703
49338
  const STOP = /* @__PURE__ */ new Set(["the", "and", "for", "with", "from", "our", "over", "of", "in", "on", "a", "an", "we", "us", "have", "been"]);
48704
49339
  const unitAfter = (text2, at) => norm4(text2.slice(at)).split(" ").slice(1).filter((w) => w && !STOP.has(w) && !/^[\d,.]+$/.test(w)).slice(0, 2);
48705
49340
  const briefFigures = b.proof.aggregates.flatMap((agg) => {
48706
49341
  const line3 = `${agg.value} ${agg.label}`;
48707
- return [...line3.matchAll(/\d[\d,.]*/g)].map((m) => ({ n: digits2(m[0]), units: /* @__PURE__ */ new Set([...unitAfter(line3, m.index ?? 0), ...norm4(agg.label).split(" ").filter((w) => w && !STOP.has(w))]) }));
49342
+ return [...line3.matchAll(/\d[\d,.]*/g)].map((m) => ({ n: digits(m[0]), units: /* @__PURE__ */ new Set([...unitAfter(line3, m.index ?? 0), ...norm4(agg.label).split(" ").filter((w) => w && !STOP.has(w))]) }));
48708
49343
  });
48709
49344
  const seenClaims = /* @__PURE__ */ new Set();
48710
49345
  const claimed = (text2) => {
48711
49346
  for (const m of text2.matchAll(/\d[\d,.]*/g)) {
48712
- const key = `${digits2(m[0])}@${norm4(text2).slice(0, 40)}`;
49347
+ const key = `${digits(m[0])}@${norm4(text2).slice(0, 40)}`;
48713
49348
  if (seenClaims.has(key)) continue;
48714
49349
  seenClaims.add(key);
48715
- const hit = briefFigures.find((f) => f.n === digits2(m[0]));
49350
+ const hit = briefFigures.find((f) => f.n === digits(m[0]));
48716
49351
  if (!hit) {
48717
49352
  invented.push(`figure "${m[0]}" in "${text2.slice(0, 30)}"`);
48718
49353
  continue;
@@ -49393,9 +50028,9 @@ async function settle(page) {
49393
50028
  }
49394
50029
  async function readTarget(target) {
49395
50030
  if (isUrl(target)) return { html: null, url: target };
49396
- const abs = path35.resolve(target);
49397
- if (!fs37.existsSync(abs)) throw new Error(`No file at ${abs}.`);
49398
- return { html: fs37.readFileSync(abs, "utf8"), url: (0, import_node_url.pathToFileURL)(abs).toString() };
50031
+ const abs = path36.resolve(target);
50032
+ if (!fs38.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50033
+ return { html: fs38.readFileSync(abs, "utf8"), url: (0, import_node_url.pathToFileURL)(abs).toString() };
49399
50034
  }
49400
50035
  function slidePixels(index, png, file2, sec, brand) {
49401
50036
  let img;
@@ -49443,7 +50078,7 @@ async function verifyDesign(opts) {
49443
50078
  const { html: html2, url } = await readTarget(opts.target);
49444
50079
  const placement = opts.placement ?? "html";
49445
50080
  const medium = opts.medium;
49446
- fs37.mkdirSync(opts.outDir, { recursive: true });
50081
+ fs38.mkdirSync(opts.outDir, { recursive: true });
49447
50082
  const files = [];
49448
50083
  const screenshots = [];
49449
50084
  const at = (/* @__PURE__ */ new Date()).toISOString();
@@ -49488,8 +50123,8 @@ async function verifyDesign(opts) {
49488
50123
  try {
49489
50124
  await loc.scrollIntoViewIfNeeded({ timeout: 5e3 });
49490
50125
  const png = await loc.screenshot({ type: "png", timeout: 15e3 });
49491
- fs37.writeFileSync(path35.join(opts.outDir, file2), png);
49492
- files.push(path35.join(opts.outDir, file2));
50126
+ fs38.writeFileSync(path36.join(opts.outDir, file2), png);
50127
+ files.push(path36.join(opts.outDir, file2));
49493
50128
  screenshots.push(file2);
49494
50129
  pixels.push(slidePixels(sec.index, png, file2, sec, opts.brand));
49495
50130
  } catch {
@@ -49521,8 +50156,8 @@ async function verifyDesign(opts) {
49521
50156
  const shot = "page-1440.png";
49522
50157
  try {
49523
50158
  const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
49524
- fs37.writeFileSync(path35.join(opts.outDir, shot), png);
49525
- files.push(path35.join(opts.outDir, shot));
50159
+ fs38.writeFileSync(path36.join(opts.outDir, shot), png);
50160
+ files.push(path36.join(opts.outDir, shot));
49526
50161
  screenshots.push(shot);
49527
50162
  } catch {
49528
50163
  }
@@ -49530,8 +50165,8 @@ async function verifyDesign(opts) {
49530
50165
  await page.setViewportSize({ width: 390, height: 844 });
49531
50166
  await page.evaluate(SETTLE_SCROLL_JS).catch(() => void 0);
49532
50167
  const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
49533
- fs37.writeFileSync(path35.join(opts.outDir, "page-390.png"), png);
49534
- files.push(path35.join(opts.outDir, "page-390.png"));
50168
+ fs38.writeFileSync(path36.join(opts.outDir, "page-390.png"), png);
50169
+ files.push(path36.join(opts.outDir, "page-390.png"));
49535
50170
  screenshots.push("page-390.png");
49536
50171
  } catch {
49537
50172
  }
@@ -49604,21 +50239,21 @@ async function verifyDesign(opts) {
49604
50239
  screenshots
49605
50240
  };
49606
50241
  const checked = VerificationSchema.parse(report2);
49607
- const jsonPath = path35.join(opts.outDir, "verification.json");
49608
- fs37.writeFileSync(jsonPath, JSON.stringify(checked, null, 2) + "\n");
50242
+ const jsonPath = path36.join(opts.outDir, "verification.json");
50243
+ fs38.writeFileSync(jsonPath, JSON.stringify(checked, null, 2) + "\n");
49609
50244
  files.push(jsonPath);
49610
50245
  const reportHtml = renderVerificationHtml(checked, opts.branding);
49611
- const htmlPath = path35.join(opts.outDir, "verification.html");
49612
- fs37.writeFileSync(htmlPath, reportHtml);
50246
+ const htmlPath = path36.join(opts.outDir, "verification.html");
50247
+ fs38.writeFileSync(htmlPath, reportHtml);
49613
50248
  files.push(htmlPath);
49614
50249
  return { report: checked, files, reportHtml };
49615
50250
  }
49616
- var fs37, path35, import_node_url, WEB_WIDTHS, SETTLE_SCROLL_JS, FOCUS_JS;
50251
+ var fs38, path36, import_node_url, WEB_WIDTHS, SETTLE_SCROLL_JS, FOCUS_JS;
49617
50252
  var init_verify = __esm({
49618
50253
  "src/command-os/design/verify.ts"() {
49619
50254
  "use strict";
49620
- fs37 = __toESM(require("node:fs"));
49621
- path35 = __toESM(require("node:path"));
50255
+ fs38 = __toESM(require("node:fs"));
50256
+ path36 = __toESM(require("node:path"));
49622
50257
  import_node_url = require("node:url");
49623
50258
  init_lint();
49624
50259
  init_probe();
@@ -50037,7 +50672,7 @@ function registerDesignStudioTools(server2) {
50037
50672
  const { dna, logoSvg, logoPng } = deriveBrandDna(raw, { name, slug: slug3, rights: { declaration: a.rights, at: (/* @__PURE__ */ new Date()).toISOString() }, browser, firecrawl: fc });
50038
50673
  if (a.rights === "none" && dna.logo) notes.push("Rights declared as none: the logo is stored for reference only; do not place it on a client-facing page.");
50039
50674
  const written = writeBrand(dna, { logoSvg, logoPng, screenshotPng: browser?.screenshotPng });
50040
- return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) => path36.basename(f)), notes };
50675
+ return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) => path37.basename(f)), notes };
50041
50676
  }
50042
50677
  );
50043
50678
  safeTool(
@@ -50141,14 +50776,14 @@ function registerDesignStudioTools(server2) {
50141
50776
  d.provenance.evidence["shape.radius"] = `set by ${stamp}`;
50142
50777
  }
50143
50778
  if (a.logoSvg) {
50144
- let clean;
50779
+ let clean2;
50145
50780
  try {
50146
- clean = sanitizeSvg(a.logoSvg);
50781
+ clean2 = sanitizeSvg(a.logoSvg);
50147
50782
  } catch (e) {
50148
50783
  throw new Error(`${e.message} Nothing was changed.`);
50149
50784
  }
50150
- a.logoSvg = clean;
50151
- d.logo = { inlineSvg: clean, whiteFillFixed: false, source: `set by ${stamp}`, rights: "declared" };
50785
+ a.logoSvg = clean2;
50786
+ d.logo = { inlineSvg: clean2, whiteFillFixed: false, source: `set by ${stamp}`, rights: "declared" };
50152
50787
  d.provenance.evidence["logo"] = `set by ${stamp}`;
50153
50788
  }
50154
50789
  if (a.tone) d.voice.tone = a.tone;
@@ -50204,9 +50839,9 @@ function registerDesignStudioTools(server2) {
50204
50839
  "Check a written HTML file honours the output contract before grading: section and role markers present and consistent, no outside scripts, one h1 on a page, every image described. Deterministic; no browser. A file that fails this is not graded by verify_design.",
50205
50840
  { file: import_zod76.z.string().describe("Path to the .html file."), medium: import_zod76.z.enum(MEDIA) },
50206
50841
  async ({ file: file2, medium }) => {
50207
- const abs = path36.resolve(file2);
50208
- if (!fs38.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50209
- const r = lintDesignContract(fs38.readFileSync(abs, "utf8"), medium);
50842
+ const abs = path37.resolve(file2);
50843
+ if (!fs39.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50844
+ const r = lintDesignContract(fs39.readFileSync(abs, "utf8"), medium);
50210
50845
  return { ok: r.ok, problems: r.problems, warnings: r.warnings, facts: r.facts };
50211
50846
  }
50212
50847
  );
@@ -50227,7 +50862,7 @@ function registerDesignStudioTools(server2) {
50227
50862
  async (a) => {
50228
50863
  const brand = a.brandSlug ? houseLooks().find((d) => d.slug === a.brandSlug) ?? readBrand(slugify(a.brandSlug)) : void 0;
50229
50864
  const isUrl2 = /^https?:\/\//i.test(a.target);
50230
- const outDir = a.outDir ?? (isUrl2 ? path36.resolve(`verify-${new URL(a.target).hostname.replace(/[^a-z0-9.-]/gi, "_")}`) : path36.join(path36.dirname(path36.resolve(a.target)), `verify-${path36.basename(a.target, ".html")}`));
50865
+ const outDir = a.outDir ?? (isUrl2 ? path37.resolve(`verify-${new URL(a.target).hostname.replace(/[^a-z0-9.-]/gi, "_")}`) : path37.join(path37.dirname(path37.resolve(a.target)), `verify-${path37.basename(a.target, ".html")}`));
50231
50866
  const r = await verifyDesign({ target: a.target, medium: a.medium, brand, outDir, placement: a.placement, mode: a.mode, ratio: a.ratio, logo: a.logo, branding: readAgencyBranding() });
50232
50867
  const v = r.report;
50233
50868
  const failed = v.rules.filter((x) => x.state === "MEASURED-FAIL").map((x) => `${x.ruleId} ${x.name}: ${x.measured ?? ""}`);
@@ -50242,7 +50877,7 @@ function registerDesignStudioTools(server2) {
50242
50877
  judgments: v.rules.filter((x) => x.state === "ASSERTED").map((x) => `${x.ruleId} ${x.name}: ${x.detail ?? ""}`),
50243
50878
  notApplicable: v.rules.filter((x) => x.state === "N-A").map((x) => `${x.ruleId}: ${x.detail ?? ""}`),
50244
50879
  brandMatch: v.brandMatch,
50245
- report: path36.join(outDir, "verification.html"),
50880
+ report: path37.join(outDir, "verification.html"),
50246
50881
  files: r.files,
50247
50882
  ...a.placement === "ghl" && { builderOwned: builderOwnedRules().map((b) => `${b.rule.id} ${b.rule.name}: ${b.why}`) }
50248
50883
  };
@@ -50270,9 +50905,9 @@ function registerDesignStudioTools(server2) {
50270
50905
  if (!a.pageDataDownloadUrl && !a.file) throw new Error("Pass pageDataDownloadUrl (from get_page_full) or file.");
50271
50906
  let page;
50272
50907
  if (a.file) {
50273
- const abs = path36.resolve(a.file);
50274
- if (!fs38.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50275
- page = JSON.parse(fs38.readFileSync(abs, "utf8"));
50908
+ const abs = path37.resolve(a.file);
50909
+ if (!fs39.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50910
+ page = JSON.parse(fs39.readFileSync(abs, "utf8"));
50276
50911
  } else {
50277
50912
  const response = await fetch(a.pageDataDownloadUrl, { headers: { Accept: "application/json,*/*" } });
50278
50913
  if (!response.ok) throw new Error(`Could not download the page data: HTTP ${response.status}. Re-read get_page_full \u2014 the download URL expires.`);
@@ -50313,9 +50948,9 @@ function registerDesignStudioTools(server2) {
50313
50948
  if (!a.file && !a.text) throw new Error("Pass file or text.");
50314
50949
  let html2 = a.text ?? "";
50315
50950
  if (a.file) {
50316
- const abs = path36.resolve(a.file);
50317
- if (!fs38.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50318
- html2 = fs38.readFileSync(abs, "utf8");
50951
+ const abs = path37.resolve(a.file);
50952
+ if (!fs39.existsSync(abs)) throw new Error(`No file at ${abs}.`);
50953
+ html2 = fs39.readFileSync(abs, "utf8");
50319
50954
  }
50320
50955
  const r = await gradeCopy(html2, claudeGenerate(a.model), { icp: a.icp });
50321
50956
  if (!r.grade) {
@@ -50350,13 +50985,13 @@ function registerDesignStudioTools(server2) {
50350
50985
  }
50351
50986
  );
50352
50987
  }
50353
- var import_zod76, fs38, path36, summarise2;
50988
+ var import_zod76, fs39, path37, summarise2;
50354
50989
  var init_design_studio = __esm({
50355
50990
  "src/tools/design-studio.ts"() {
50356
50991
  "use strict";
50357
50992
  import_zod76 = require("zod");
50358
- fs38 = __toESM(require("node:fs"));
50359
- path36 = __toESM(require("node:path"));
50993
+ fs39 = __toESM(require("node:fs"));
50994
+ path37 = __toESM(require("node:path"));
50360
50995
  init_tool_helpers();
50361
50996
  init_extract();
50362
50997
  init_extract_browser();
@@ -51080,11 +51715,11 @@ var init_audit = __esm({
51080
51715
  // src/command-os/delivery-audit/store.ts
51081
51716
  function deliveryAuditPath() {
51082
51717
  const override = process.env[DELIVERY_AUDIT_ENV];
51083
- return override && override.trim() ? override : path37.join(appDataDir(), "delivery-audit.json");
51718
+ return override && override.trim() ? override : path38.join(appDataDir(), "delivery-audit.json");
51084
51719
  }
51085
51720
  function readDeliveryAudit() {
51086
51721
  try {
51087
- const parsed2 = JSON.parse(fs39.readFileSync(deliveryAuditPath(), "utf-8"));
51722
+ const parsed2 = JSON.parse(fs40.readFileSync(deliveryAuditPath(), "utf-8"));
51088
51723
  if (!parsed2 || typeof parsed2 !== "object" || !parsed2.answers || typeof parsed2.answers !== "object") return { ...EMPTY3 };
51089
51724
  return { v: 1, answers: parsed2.answers, updated_at: parsed2.updated_at };
51090
51725
  } catch {
@@ -51095,7 +51730,7 @@ function readForWrite2() {
51095
51730
  const target = deliveryAuditPath();
51096
51731
  let raw;
51097
51732
  try {
51098
- raw = fs39.readFileSync(target, "utf-8");
51733
+ raw = fs40.readFileSync(target, "utf-8");
51099
51734
  } catch (e) {
51100
51735
  if (e?.code === "ENOENT") return { ...EMPTY3 };
51101
51736
  throw new Error("Your saved audit could not be read, so nothing was changed. Nothing was lost.");
@@ -51107,7 +51742,7 @@ function readForWrite2() {
51107
51742
  } catch {
51108
51743
  const kept = `${target}.unreadable-${Date.now()}`;
51109
51744
  try {
51110
- fs39.copyFileSync(target, kept);
51745
+ fs40.copyFileSync(target, kept);
51111
51746
  } catch {
51112
51747
  }
51113
51748
  throw new Error(`Your saved audit is damaged, so nothing was written over it. A copy is at ${kept}. Start again with reset:true when you are ready.`);
@@ -51119,13 +51754,13 @@ function writeFile2(f) {
51119
51754
  const tmp = `${target}.${process.pid}.tmp`;
51120
51755
  let renamed = false;
51121
51756
  try {
51122
- fs39.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
51123
- fs39.renameSync(tmp, target);
51757
+ fs40.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
51758
+ fs40.renameSync(tmp, target);
51124
51759
  renamed = true;
51125
51760
  } finally {
51126
51761
  if (!renamed) {
51127
51762
  try {
51128
- fs39.unlinkSync(tmp);
51763
+ fs40.unlinkSync(tmp);
51129
51764
  } catch {
51130
51765
  }
51131
51766
  }
@@ -51146,12 +51781,12 @@ function resetDeliveryAudit(now = () => /* @__PURE__ */ new Date()) {
51146
51781
  writeFile2(next);
51147
51782
  return next;
51148
51783
  }
51149
- var fs39, path37, EMPTY3, DELIVERY_AUDIT_ENV;
51784
+ var fs40, path38, EMPTY3, DELIVERY_AUDIT_ENV;
51150
51785
  var init_store4 = __esm({
51151
51786
  "src/command-os/delivery-audit/store.ts"() {
51152
51787
  "use strict";
51153
- fs39 = __toESM(require("node:fs"));
51154
- path37 = __toESM(require("node:path"));
51788
+ fs40 = __toESM(require("node:fs"));
51789
+ path38 = __toESM(require("node:path"));
51155
51790
  init_credentials_store();
51156
51791
  EMPTY3 = { v: 1, answers: {} };
51157
51792
  DELIVERY_AUDIT_ENV = "GHL_MCP_DELIVERY_AUDIT";
@@ -51472,7 +52107,7 @@ function position(a) {
51472
52107
  const cyclic = a.steps.length > 0 && check.order.length === 0;
51473
52108
  const order = check.order.length ? check.order : a.steps.map((s) => s.id);
51474
52109
  const lastAttempt = /* @__PURE__ */ new Map();
51475
- for (const x of [...a.applied].sort((p, q2) => p.at < q2.at ? -1 : p.at > q2.at ? 1 : 0)) {
52110
+ for (const x of [...a.applied].sort((p, q3) => p.at < q3.at ? -1 : p.at > q3.at ? 1 : 0)) {
51476
52111
  lastAttempt.set(x.stepId, x);
51477
52112
  }
51478
52113
  const doneIds = new Set([...lastAttempt.values()].filter((x) => x.state === "applied").map((x) => x.stepId));
@@ -51515,7 +52150,7 @@ function position(a) {
51515
52150
  line3 = `STALLED before it started: this plan's steps wait on each other (${names}), so there is no order that works. It cannot be run as it stands and needs planning again.`;
51516
52151
  } else if (failed.length) {
51517
52152
  phase = "stalled";
51518
- const f = failed.reduce((p, q2) => q2.at > p.at ? q2 : p);
52153
+ const f = failed.reduce((p, q3) => q3.at > p.at ? q3 : p);
51519
52154
  stalledOn = { step: f.stepId, because: f.because ?? "no reason was recorded" };
51520
52155
  const rest = total - done - 1;
51521
52156
  line3 = `STALLED on ${f.stepId} (${byId2.get(f.stepId)?.why ?? "step"}): ${f.because ?? "no reason was recorded"}. ${done} of ${total} steps are done and the remaining ${rest === 1 ? "step is" : `${rest} steps are`} on hold \u2014 nothing else runs until this one is sorted, even the steps that do not depend on it, because the plan's order is part of what was approved. Fix what went wrong and record ${f.stepId} again with retry, or abandon the build.`;
@@ -51607,7 +52242,7 @@ var init_automation = __esm({
51607
52242
  // src/command-os/build/store.ts
51608
52243
  function automationsPath() {
51609
52244
  const override = process.env[AUTOMATIONS_ENV];
51610
- return override && override.trim() ? override : path38.join(appDataDir(), "automations.json");
52245
+ return override && override.trim() ? override : path39.join(appDataDir(), "automations.json");
51611
52246
  }
51612
52247
  function parse4(raw) {
51613
52248
  const p = JSON.parse(raw);
@@ -51616,7 +52251,7 @@ function parse4(raw) {
51616
52251
  }
51617
52252
  function readAutomations() {
51618
52253
  try {
51619
- return parse4(fs40.readFileSync(automationsPath(), "utf-8")).builds;
52254
+ return parse4(fs41.readFileSync(automationsPath(), "utf-8")).builds;
51620
52255
  } catch {
51621
52256
  return { ...EMPTY4.builds };
51622
52257
  }
@@ -51625,7 +52260,7 @@ function readForWrite3() {
51625
52260
  const target = automationsPath();
51626
52261
  let raw;
51627
52262
  try {
51628
- raw = fs40.readFileSync(target, "utf-8");
52263
+ raw = fs41.readFileSync(target, "utf-8");
51629
52264
  } catch (e) {
51630
52265
  if (e?.code === "ENOENT") return { v: 1, builds: {} };
51631
52266
  throw new Error("Your builds file could not be read, so nothing was changed. Nothing was lost.");
@@ -51635,7 +52270,7 @@ function readForWrite3() {
51635
52270
  } catch {
51636
52271
  const kept = `${target}.unreadable-${Date.now()}`;
51637
52272
  try {
51638
- fs40.copyFileSync(target, kept);
52273
+ fs41.copyFileSync(target, kept);
51639
52274
  } catch {
51640
52275
  }
51641
52276
  throw new Error(
@@ -51649,13 +52284,13 @@ function writeFile3(f) {
51649
52284
  const tmp = `${target}.${process.pid}.tmp`;
51650
52285
  let renamed = false;
51651
52286
  try {
51652
- fs40.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
51653
- fs40.renameSync(tmp, target);
52287
+ fs41.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
52288
+ fs41.renameSync(tmp, target);
51654
52289
  renamed = true;
51655
52290
  } finally {
51656
52291
  if (!renamed) {
51657
52292
  try {
51658
- fs40.unlinkSync(tmp);
52293
+ fs41.unlinkSync(tmp);
51659
52294
  } catch {
51660
52295
  }
51661
52296
  }
@@ -51681,12 +52316,12 @@ function newAutomationId(now = () => /* @__PURE__ */ new Date()) {
51681
52316
  }
51682
52317
  return id;
51683
52318
  }
51684
- var fs40, path38, AUTOMATIONS_ENV, EMPTY4;
52319
+ var fs41, path39, AUTOMATIONS_ENV, EMPTY4;
51685
52320
  var init_store5 = __esm({
51686
52321
  "src/command-os/build/store.ts"() {
51687
52322
  "use strict";
51688
- fs40 = __toESM(require("node:fs"));
51689
- path38 = __toESM(require("node:path"));
52323
+ fs41 = __toESM(require("node:fs"));
52324
+ path39 = __toESM(require("node:path"));
51690
52325
  init_credentials_store();
51691
52326
  AUTOMATIONS_ENV = "GHL_MCP_AUTOMATIONS";
51692
52327
  EMPTY4 = { v: 1, builds: {} };
@@ -52220,15 +52855,15 @@ var init_agency_build = __esm({
52220
52855
 
52221
52856
  // src/tools/client-engagements.ts
52222
52857
  function resolveClient(who, roster) {
52223
- const q2 = who.trim();
52224
- if (!q2) return { error: "Name who you mean \u2014 either the way you refer to them, or their account id." };
52225
- const byId2 = roster.find((r) => r.locationId === q2);
52858
+ const q3 = who.trim();
52859
+ if (!q3) return { error: "Name who you mean \u2014 either the way you refer to them, or their account id." };
52860
+ const byId2 = roster.find((r) => r.locationId === q3);
52226
52861
  if (byId2) return { locationId: byId2.locationId, name: byId2.name, onTheBoard: true };
52227
- const hits = roster.filter((r) => r.name?.trim().toLowerCase() === q2.toLowerCase());
52228
- const loose2 = hits.length ? hits : roster.filter((r) => r.name?.toLowerCase().includes(q2.toLowerCase()));
52862
+ const hits = roster.filter((r) => r.name?.trim().toLowerCase() === q3.toLowerCase());
52863
+ const loose2 = hits.length ? hits : roster.filter((r) => r.name?.toLowerCase().includes(q3.toLowerCase()));
52229
52864
  if (loose2.length === 1) return { locationId: loose2[0].locationId, name: loose2[0].name, onTheBoard: true };
52230
- if (loose2.length > 1) return { error: `More than one match for "${q2}": ${loose2.map((r) => r.name).join(", ")}. Use the full name.` };
52231
- return { error: `Nothing called "${q2}" is on your board or in your records.` };
52865
+ if (loose2.length > 1) return { error: `More than one match for "${q3}": ${loose2.map((r) => r.name).join(", ")}. Use the full name.` };
52866
+ return { error: `Nothing called "${q3}" is on your board or in your records.` };
52232
52867
  }
52233
52868
  function line2(locationId2, e, roster, profile) {
52234
52869
  const boardName = roster.find((r) => r.locationId === locationId2)?.name;
@@ -52483,9 +53118,9 @@ function looksLikeRawId(word) {
52483
53118
  if (/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(word)) return true;
52484
53119
  if (/^[0-9a-f]{24}$/.test(word)) return true;
52485
53120
  if (!/^[A-Za-z0-9]{17,}$/.test(word)) return false;
52486
- const digits2 = (word.match(/\d/g) ?? []).length;
53121
+ const digits = (word.match(/\d/g) ?? []).length;
52487
53122
  const letters = (word.match(/[A-Za-z]/g) ?? []).length;
52488
- if (digits2 < 2 || letters < 2) return false;
53123
+ if (digits < 2 || letters < 2) return false;
52489
53124
  const longestLetterRun = Math.max(0, ...(word.match(/[a-z]+|[A-Z]+/g) ?? []).map((r) => r.length));
52490
53125
  return longestLetterRun <= 3;
52491
53126
  }
@@ -53532,7 +54167,7 @@ function planRevert(record, opts) {
53532
54167
  function errText(e) {
53533
54168
  return e instanceof Error ? e.message : String(e);
53534
54169
  }
53535
- async function executeRevert(plan, deps, wait = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms))) {
54170
+ async function executeRevert(plan, deps, wait = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms))) {
53536
54171
  const removed = [];
53537
54172
  const refused = [];
53538
54173
  const attempted = [];
@@ -53651,8 +54286,8 @@ function renderDryRun(plan, opts = {}) {
53651
54286
  const children2 = withParent.filter((w) => w.object.parentRef && g.items.some((i) => i.object.ref === w.object.parentRef));
53652
54287
  if (children2.length) {
53653
54288
  const kind = children2[0].object.type;
53654
- const inside2 = g.items.length === 1 ? "inside it" : "inside them";
53655
- L.push(` (plus the ${children2.length} ${PLURAL[kind].toLowerCase()} ${inside2}, which GoHighLevel removes at the same time)`);
54289
+ const inside3 = g.items.length === 1 ? "inside it" : "inside them";
54290
+ L.push(` (plus the ${children2.length} ${PLURAL[kind].toLowerCase()} ${inside3}, which GoHighLevel removes at the same time)`);
53656
54291
  }
53657
54292
  }
53658
54293
  const kept = [...bound, ...cannot, ...gone];
@@ -54845,11 +55480,11 @@ function extractFormFields(formFull) {
54845
55480
  const fields = Array.isArray(inner.fields) ? inner.fields : [];
54846
55481
  return fields.filter((f) => !!f && typeof f === "object");
54847
55482
  }
54848
- function findRecordForQuestion(q2, records) {
54849
- const wantKey = expectedFieldKey(q2).toLowerCase();
55483
+ function findRecordForQuestion(q3, records) {
55484
+ const wantKey = expectedFieldKey(q3).toLowerCase();
54850
55485
  const byKey = records.find((r) => r.fieldKey.toLowerCase() === wantKey);
54851
55486
  if (byKey) return byKey;
54852
- const wantName = intakeFieldName(q2.label).toLowerCase();
55487
+ const wantName = intakeFieldName(q3.label).toLowerCase();
54853
55488
  return records.find((r) => r.name.toLowerCase() === wantName);
54854
55489
  }
54855
55490
  function isFormNotYetPropagated(error) {
@@ -55074,13 +55709,13 @@ ${text3}`);
55074
55709
  };
55075
55710
  }
55076
55711
  function makePipelineApi(builderClient) {
55077
- return async (method, path41, body) => {
55078
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path41}`;
55712
+ return async (method, path42, body) => {
55713
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path42}`;
55079
55714
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
55080
55715
  const response = await builderRequest(builderClient, method, url, sendsBody ? { body } : {});
55081
55716
  if (!response.ok) {
55082
55717
  const text3 = await response.text();
55083
- throw new Error(`Pipeline API ${response.status}: ${method} ${path41}
55718
+ throw new Error(`Pipeline API ${response.status}: ${method} ${path42}
55084
55719
  ${text3}`);
55085
55720
  }
55086
55721
  const text2 = await response.text();
@@ -55093,8 +55728,8 @@ ${text3}`);
55093
55728
  };
55094
55729
  }
55095
55730
  function makeFunnelApi(builderClient) {
55096
- return async (method, path41, body) => {
55097
- const url = `https://backend.leadconnectorhq.com/funnels${path41}`;
55731
+ return async (method, path42, body) => {
55732
+ const url = `https://backend.leadconnectorhq.com/funnels${path42}`;
55098
55733
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
55099
55734
  const response = await builderRequest(builderClient, method, url, {
55100
55735
  ...sendsBody ? { body } : {},
@@ -55107,7 +55742,7 @@ function makeFunnelApi(builderClient) {
55107
55742
  });
55108
55743
  if (!response.ok) {
55109
55744
  const text3 = await response.text();
55110
- throw new Error(`Funnel API ${response.status}: ${method} ${path41}
55745
+ throw new Error(`Funnel API ${response.status}: ${method} ${path42}
55111
55746
  ${text3.slice(0, 300)}`);
55112
55747
  }
55113
55748
  const text2 = await response.text();
@@ -55474,17 +56109,17 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
55474
56109
  industry: industryPack(industry)?.slug,
55475
56110
  intake: describeComposition({ industry, agency }),
55476
56111
  count: questions.length,
55477
- questions: questions.map((q2) => ({
55478
- key: q2.key,
55479
- label: q2.label,
55480
- section: q2.sectionLabel,
55481
- required: q2.required,
55482
- fieldKind: q2.field.kind,
55483
- dataType: q2.field.kind === "custom" ? q2.field.dataType : void 0,
55484
- standardTag: q2.field.kind === "standard" ? q2.field.tag : void 0,
55485
- expectedFieldKey: q2.field.kind === "custom" ? expectedFieldKey(q2) : void 0,
55486
- options: q2.options,
55487
- briefPath: q2.briefPath
56112
+ questions: questions.map((q3) => ({
56113
+ key: q3.key,
56114
+ label: q3.label,
56115
+ section: q3.sectionLabel,
56116
+ required: q3.required,
56117
+ fieldKind: q3.field.kind,
56118
+ dataType: q3.field.kind === "custom" ? q3.field.dataType : void 0,
56119
+ standardTag: q3.field.kind === "standard" ? q3.field.tag : void 0,
56120
+ expectedFieldKey: q3.field.kind === "custom" ? expectedFieldKey(q3) : void 0,
56121
+ options: q3.options,
56122
+ briefPath: q3.briefPath
55488
56123
  }))
55489
56124
  };
55490
56125
  }
@@ -56088,26 +56723,26 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
56088
56723
  if (!builderClient) return;
56089
56724
  const bc = builderClient;
56090
56725
  async function resolveCustomFields(locationId2, dryRun, questions) {
56091
- const cqs = questions.filter((q2) => q2.field.kind === "custom");
56726
+ const cqs = questions.filter((q3) => q3.field.kind === "custom");
56092
56727
  const existing = parseCustomFields(await client.get(`/locations/${locationId2}/customFields`));
56093
56728
  const created = [];
56094
56729
  const reused = [];
56095
- for (const q2 of cqs) {
56096
- const found = findRecordForQuestion(q2, existing);
56730
+ for (const q3 of cqs) {
56731
+ const found = findRecordForQuestion(q3, existing);
56097
56732
  if (found) {
56098
- reused.push(q2.label);
56733
+ reused.push(q3.label);
56099
56734
  } else {
56100
- created.push(q2.label);
56735
+ created.push(q3.label);
56101
56736
  if (!dryRun) {
56102
56737
  const body = {
56103
- name: intakeFieldName(q2.label),
56104
- dataType: q2.field.kind === "custom" ? q2.field.dataType : "TEXT",
56738
+ name: intakeFieldName(q3.label),
56739
+ dataType: q3.field.kind === "custom" ? q3.field.dataType : "TEXT",
56105
56740
  model: "contact"
56106
56741
  };
56107
- if (q2.options && q2.field.kind === "custom") {
56108
- const dt = q2.field.dataType;
56742
+ if (q3.options && q3.field.kind === "custom") {
56743
+ const dt = q3.field.dataType;
56109
56744
  if (dt === "SINGLE_OPTIONS" || dt === "MULTIPLE_OPTIONS" || dt === "CHECKBOX") {
56110
- body.options = [...q2.options];
56745
+ body.options = [...q3.options];
56111
56746
  }
56112
56747
  }
56113
56748
  await client.post(`/locations/${locationId2}/customFields`, { body });
@@ -56121,13 +56756,13 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
56121
56756
  const after = parseCustomFields(await client.get(`/locations/${locationId2}/customFields`));
56122
56757
  resolved2.clear();
56123
56758
  missing = void 0;
56124
- for (const q2 of cqs) {
56125
- const rec = findRecordForQuestion(q2, after);
56759
+ for (const q3 of cqs) {
56760
+ const rec = findRecordForQuestion(q3, after);
56126
56761
  if (!rec) {
56127
- missing = q2;
56762
+ missing = q3;
56128
56763
  break;
56129
56764
  }
56130
- resolved2.set(q2.key, rec);
56765
+ resolved2.set(q3.key, rec);
56131
56766
  }
56132
56767
  if (!missing) break;
56133
56768
  if (attempt < 6) await sleep6(700 * attempt);
@@ -56177,7 +56812,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
56177
56812
  industry: pack?.slug,
56178
56813
  intake,
56179
56814
  questionCount: questions.length,
56180
- standardFields: questions.filter((q2) => q2.field.kind === "standard").map((q2) => q2.label),
56815
+ standardFields: questions.filter((q3) => q3.field.kind === "standard").map((q3) => q3.label),
56181
56816
  customFieldsToCreate: created,
56182
56817
  customFieldsToReuse: reused,
56183
56818
  note: "No changes written. Re-run without dryRun to install."
@@ -56422,7 +57057,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env2 = process
56422
57057
  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.` }));
56423
57058
  }
56424
57059
  const reader = token2 ? new GHLClient({ apiKey: token2.apiKey, locationId: loc }) : client;
56425
- return refuseUnseenFindings2((path41, params) => reader.get(path41, params ? { params } : void 0), loc, findings);
57060
+ return refuseUnseenFindings2((path42, params) => reader.get(path42, params ? { params } : void 0), loc, findings);
56426
57061
  }, () => client.defaultLocationId);
56427
57062
  registerClientEngagementTools(wrap(CLIENT_ENGAGEMENTS_MODULE), registry2);
56428
57063
  registerAuditReportTools(wrap(AUDIT_REPORT_MODULE));
@@ -56842,8 +57477,8 @@ __export(index_exports, {
56842
57477
  });
56843
57478
  module.exports = __toCommonJS(index_exports);
56844
57479
  var dotenv2 = __toESM(require("dotenv"));
56845
- var path40 = __toESM(require("path"));
56846
- var fs42 = __toESM(require("fs"));
57480
+ var path41 = __toESM(require("path"));
57481
+ var fs43 = __toESM(require("fs"));
56847
57482
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
56848
57483
 
56849
57484
  // src/version-compare.ts
@@ -56915,8 +57550,8 @@ var import_zod87 = require("zod");
56915
57550
 
56916
57551
  // src/skill-installer.ts
56917
57552
  var import_node_crypto13 = require("node:crypto");
56918
- var fs41 = __toESM(require("node:fs"));
56919
- var path39 = __toESM(require("node:path"));
57553
+ var fs42 = __toESM(require("node:fs"));
57554
+ var path40 = __toESM(require("node:path"));
56920
57555
  var os7 = __toESM(require("node:os"));
56921
57556
  function sha2562(buf) {
56922
57557
  return (0, import_node_crypto13.createHash)("sha256").update(buf).digest("hex");
@@ -56931,9 +57566,9 @@ function isOlderRelease(candidate, installed) {
56931
57566
  return false;
56932
57567
  }
56933
57568
  function bundledSkillsDir(baseDir) {
56934
- const candidate = path39.resolve(baseDir, "..", "skills");
57569
+ const candidate = path40.resolve(baseDir, "..", "skills");
56935
57570
  try {
56936
- return fs41.statSync(candidate).isDirectory() ? candidate : null;
57571
+ return fs42.statSync(candidate).isDirectory() ? candidate : null;
56937
57572
  } catch {
56938
57573
  return null;
56939
57574
  }
@@ -56941,10 +57576,10 @@ function bundledSkillsDir(baseDir) {
56941
57576
  function listFiles(root) {
56942
57577
  const out = [];
56943
57578
  const walk = (dir) => {
56944
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
56945
- const full = path39.join(dir, entry.name);
57579
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
57580
+ const full = path40.join(dir, entry.name);
56946
57581
  if (entry.isDirectory()) walk(full);
56947
- else if (entry.isFile()) out.push(path39.relative(root, full));
57582
+ else if (entry.isFile()) out.push(path40.relative(root, full));
56948
57583
  }
56949
57584
  };
56950
57585
  walk(root);
@@ -56952,7 +57587,7 @@ function listFiles(root) {
56952
57587
  }
56953
57588
  function readMarker(markerPath) {
56954
57589
  try {
56955
- const parsed2 = JSON.parse(fs41.readFileSync(markerPath, "utf8"));
57590
+ const parsed2 = JSON.parse(fs42.readFileSync(markerPath, "utf8"));
56956
57591
  return parsed2 && typeof parsed2 === "object" && parsed2.files ? parsed2 : null;
56957
57592
  } catch {
56958
57593
  return null;
@@ -56966,35 +57601,35 @@ function installBundledSkills(opts) {
56966
57601
  skippedOlderPackage: [],
56967
57602
  unchanged: [],
56968
57603
  errors: [],
56969
- targetDir: opts.targetDir ?? path39.join(os7.homedir(), ".claude", "skills"),
57604
+ targetDir: opts.targetDir ?? path40.join(os7.homedir(), ".claude", "skills"),
56970
57605
  bundledDir: null
56971
57606
  };
56972
57607
  const bundled = bundledSkillsDir(opts.baseDir);
56973
57608
  result.bundledDir = bundled;
56974
57609
  if (!bundled) return result;
56975
57610
  try {
56976
- fs41.mkdirSync(result.targetDir, { recursive: true });
57611
+ fs42.mkdirSync(result.targetDir, { recursive: true });
56977
57612
  } catch (e) {
56978
57613
  result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
56979
57614
  return result;
56980
57615
  }
56981
- const markerPath = path39.join(result.targetDir, ".ghl-command-skills.json");
57616
+ const markerPath = path40.join(result.targetDir, ".ghl-command-skills.json");
56982
57617
  const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
56983
57618
  const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
56984
57619
  for (const rel of listFiles(bundled)) {
56985
57620
  try {
56986
- const src = fs41.readFileSync(path39.join(bundled, rel));
56987
- const dest = path39.join(result.targetDir, rel);
57621
+ const src = fs42.readFileSync(path40.join(bundled, rel));
57622
+ const dest = path40.join(result.targetDir, rel);
56988
57623
  const srcHash = sha2562(src);
56989
57624
  let destBuf = null;
56990
57625
  try {
56991
- destBuf = fs41.readFileSync(dest);
57626
+ destBuf = fs42.readFileSync(dest);
56992
57627
  } catch {
56993
57628
  destBuf = null;
56994
57629
  }
56995
57630
  if (destBuf === null) {
56996
- fs41.mkdirSync(path39.dirname(dest), { recursive: true });
56997
- fs41.writeFileSync(dest, src);
57631
+ fs42.mkdirSync(path40.dirname(dest), { recursive: true });
57632
+ fs42.writeFileSync(dest, src);
56998
57633
  newMarker.files[rel] = srcHash;
56999
57634
  result.installed.push(rel);
57000
57635
  continue;
@@ -57011,7 +57646,7 @@ function installBundledSkills(opts) {
57011
57646
  newMarker.packageVersion = marker.packageVersion;
57012
57647
  result.skippedOlderPackage.push(rel);
57013
57648
  } else if (lastWritten && destHash === lastWritten) {
57014
- fs41.writeFileSync(dest, src);
57649
+ fs42.writeFileSync(dest, src);
57015
57650
  newMarker.files[rel] = srcHash;
57016
57651
  result.updated.push(rel);
57017
57652
  } else {
@@ -57022,7 +57657,7 @@ function installBundledSkills(opts) {
57022
57657
  }
57023
57658
  }
57024
57659
  try {
57025
- fs41.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
57660
+ fs42.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
57026
57661
  } catch (e) {
57027
57662
  result.errors.push(`marker write failed: ${String(e)}`);
57028
57663
  }
@@ -57178,7 +57813,7 @@ dotenv2.config();
57178
57813
  setPkgVersion(pkg.version);
57179
57814
  {
57180
57815
  const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
57181
- if (configDirOverride && !path40.isAbsolute(configDirOverride)) {
57816
+ if (configDirOverride && !path41.isAbsolute(configDirOverride)) {
57182
57817
  process.stderr.write(
57183
57818
  `[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.
57184
57819
  `
@@ -57191,20 +57826,20 @@ process.on("unhandledRejection", (reason3) => {
57191
57826
  `);
57192
57827
  });
57193
57828
  function hardenSecretFilePerms() {
57194
- const repoDir = path40.resolve(__dirname, "..");
57829
+ const repoDir = path41.resolve(__dirname, "..");
57195
57830
  const candidates = [
57196
- { file: path40.join(repoDir, "start-mcp.sh"), mode: 448 },
57831
+ { file: path41.join(repoDir, "start-mcp.sh"), mode: 448 },
57197
57832
  // Legacy registry location (pre-migration); new location lives in app-data.
57198
- { file: path40.join(repoDir, ".ghl-tokens.json"), mode: 384 },
57833
+ { file: path41.join(repoDir, ".ghl-tokens.json"), mode: 384 },
57199
57834
  { file: tokenRegistryPath(), mode: 384 }
57200
57835
  ];
57201
57836
  for (const { file: file2, mode } of candidates) {
57202
57837
  let current;
57203
57838
  try {
57204
- if (!fs42.existsSync(file2)) continue;
57205
- current = fs42.statSync(file2).mode & 511;
57839
+ if (!fs43.existsSync(file2)) continue;
57840
+ current = fs43.statSync(file2).mode & 511;
57206
57841
  if (current !== mode) {
57207
- fs42.chmodSync(file2, mode);
57842
+ fs43.chmodSync(file2, mode);
57208
57843
  }
57209
57844
  } catch (error) {
57210
57845
  const message = error instanceof Error ? error.message : String(error);