@elitedcs/ghl-mcp 3.73.0 → 3.74.0

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
@@ -114,8 +114,8 @@ var init_ghl_client = __esm({
114
114
  Version: version || GHL_API_VERSION
115
115
  };
116
116
  }
117
- buildUrl(path26, params) {
118
- const url = new URL(path26, GHL_BASE_URL);
117
+ buildUrl(path27, params) {
118
+ const url = new URL(path27, GHL_BASE_URL);
119
119
  if (params) {
120
120
  for (const [key, value] of Object.entries(params)) {
121
121
  if (value !== void 0 && value !== null) {
@@ -125,8 +125,8 @@ var init_ghl_client = __esm({
125
125
  }
126
126
  return url.toString();
127
127
  }
128
- async request(method, path26, options = {}, attempt = 0) {
129
- const url = this.buildUrl(path26, options.params);
128
+ async request(method, path27, options = {}, attempt = 0) {
129
+ const url = this.buildUrl(path27, options.params);
130
130
  const headers = this.buildHeaders(options.version);
131
131
  const fetchOptions = {
132
132
  method,
@@ -144,14 +144,14 @@ var init_ghl_client = __esm({
144
144
  } catch (error) {
145
145
  clearTimeout(timeout);
146
146
  if (error instanceof Error && error.name === "AbortError") {
147
- throw new Error(`Request timeout (30s): ${method} ${path26}`);
147
+ throw new Error(`Request timeout (30s): ${method} ${path27}`);
148
148
  }
149
149
  if (!options.noRetry && attempt < MAX_RETRIES) {
150
150
  const delay5 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
151
- process.stderr.write(`[ghl-mcp] Network error on ${method} ${path26}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
151
+ process.stderr.write(`[ghl-mcp] Network error on ${method} ${path27}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
152
152
  `);
153
153
  await new Promise((r) => setTimeout(r, delay5));
154
- return this.request(method, path26, options, attempt + 1);
154
+ return this.request(method, path27, options, attempt + 1);
155
155
  }
156
156
  throw error;
157
157
  } finally {
@@ -159,10 +159,10 @@ var init_ghl_client = __esm({
159
159
  }
160
160
  if (!options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
161
161
  const delay5 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
162
- process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path26}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
162
+ process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path27}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
163
163
  `);
164
164
  await new Promise((r) => setTimeout(r, delay5));
165
- return this.request(method, path26, options, attempt + 1);
165
+ return this.request(method, path27, options, attempt + 1);
166
166
  }
167
167
  if (!response.ok) {
168
168
  let errorBody = "";
@@ -171,7 +171,7 @@ var init_ghl_client = __esm({
171
171
  } catch {
172
172
  }
173
173
  throw new Error(
174
- `GHL API Error ${response.status} ${response.statusText}: ${method} ${path26}
174
+ `GHL API Error ${response.status} ${response.statusText}: ${method} ${path27}
175
175
  ${errorBody}`
176
176
  );
177
177
  }
@@ -183,20 +183,20 @@ ${errorBody}`
183
183
  return { message: text };
184
184
  }
185
185
  }
186
- async get(path26, options) {
187
- return this.request("GET", path26, options);
186
+ async get(path27, options) {
187
+ return this.request("GET", path27, options);
188
188
  }
189
- async post(path26, options) {
190
- return this.request("POST", path26, options);
189
+ async post(path27, options) {
190
+ return this.request("POST", path27, options);
191
191
  }
192
- async put(path26, options) {
193
- return this.request("PUT", path26, options);
192
+ async put(path27, options) {
193
+ return this.request("PUT", path27, options);
194
194
  }
195
- async patch(path26, options) {
196
- return this.request("PATCH", path26, options);
195
+ async patch(path27, options) {
196
+ return this.request("PATCH", path27, options);
197
197
  }
198
- async delete(path26, options) {
199
- return this.request("DELETE", path26, options);
198
+ async delete(path27, options) {
199
+ return this.request("DELETE", path27, options);
200
200
  }
201
201
  /**
202
202
  * Helper: resolves locationId from args or falls back to default
@@ -2354,6 +2354,7 @@ var init_product_manifest = __esm({
2354
2354
  "use strict";
2355
2355
  COMMAND_OS_ONLY_TOOLS = /* @__PURE__ */ new Set([]);
2356
2356
  COMMAND_OS_ONLY_MODULES = /* @__PURE__ */ new Set([
2357
+ "connectors",
2357
2358
  "assessment",
2358
2359
  "agency-profile",
2359
2360
  "client-engagements",
@@ -2438,6 +2439,30 @@ var init_product_manifest = __esm({
2438
2439
  does: "Writes the finished assessment as a complete page on your own machine, in your branding, ready to hand to the prospect. It loads nothing from anywhere, so it opens on any host in any browser; it is marked not-to-be-indexed and given a random filename, because it carries another business's revenue and customer numbers.",
2439
2440
  why: "We never publish it. It is your prospect's data on your letterhead, so where it goes is yours to decide \u2014 and nothing in the $97 product produces a client-facing document at all.",
2440
2441
  layer: "Layer 4 \u2014 Delivery"
2442
+ },
2443
+ {
2444
+ name: "connect_system",
2445
+ does: "Stores a connector's credentials on YOUR machine so Command OS can read that system. Written beside your GoHighLevel credentials, readable only by you, never sent anywhere and never returned by any tool.",
2446
+ why: "The $97 product manages one GoHighLevel account. Reading your Stripe to learn what each client pays is about the agency's own business, not a client's account.",
2447
+ layer: "Layer 2 \u2014 Data"
2448
+ },
2449
+ {
2450
+ name: "list_connected_systems",
2451
+ does: "Which systems Command OS can read on this machine, and when each was connected. Never shows a key.",
2452
+ why: "It reports on the agency's own stack rather than a client's account, and the $97 product has no concept of a system beyond the one GoHighLevel account it is pointed at.",
2453
+ layer: "Layer 2 \u2014 Data"
2454
+ },
2455
+ {
2456
+ name: "disconnect_system",
2457
+ does: "Removes a connector's credentials from this machine. Anything already written into your client records stays.",
2458
+ why: "It removes access to the agency's own billing system, which is a thing the $97 product never had access to in the first place.",
2459
+ layer: "Layer 2 \u2014 Data"
2460
+ },
2461
+ {
2462
+ name: "sync_stripe",
2463
+ does: "Reads your Stripe and fills in what your client records do not know: what each client pays, when they renew, and who owes you money. Shows you every change before making any, and reports a customer it cannot match to one of your clients rather than guessing.",
2464
+ why: "This is the record your daily brief already reads and finds empty. Nothing in the $97 product reads your billing, and nothing in it knows which of your clients a payment belongs to.",
2465
+ layer: "Layer 2 \u2014 Data"
2441
2466
  }
2442
2467
  ];
2443
2468
  CATALOGUED_TOOLS = new Set(COMMAND_OS_CATALOGUE.map((t) => t.name));
@@ -6061,16 +6086,16 @@ function registerEmailTools(server2, client) {
6061
6086
  function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
6062
6087
  const client = builderClient;
6063
6088
  if (!client) return;
6064
- async function builderRequest(method, path26, body) {
6089
+ async function builderRequest(method, path27, body) {
6065
6090
  const headers = await client.buildHeaders();
6066
- const response = await fetch(`${EMAIL_BUILDER_BASE}${path26}`, {
6091
+ const response = await fetch(`${EMAIL_BUILDER_BASE}${path27}`, {
6067
6092
  method,
6068
6093
  headers,
6069
6094
  body: body ? JSON.stringify(body) : void 0
6070
6095
  });
6071
6096
  if (!response.ok) {
6072
6097
  const text2 = await response.text();
6073
- throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path26}
6098
+ throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path27}
6074
6099
  ${text2}`);
6075
6100
  }
6076
6101
  const text = await response.text();
@@ -7616,23 +7641,23 @@ var init_workflow_builder = __esm({
7616
7641
  function registerFunnelBuilderTools(server2, builderClient) {
7617
7642
  const client = builderClient;
7618
7643
  if (!client) return;
7619
- async function internalGet(path26) {
7620
- return client.request("GET", path26);
7644
+ async function internalGet(path27) {
7645
+ return client.request("GET", path27);
7621
7646
  }
7622
- async function internalPost(path26, body) {
7623
- return client.request("POST", path26, body);
7647
+ async function internalPost(path27, body) {
7648
+ return client.request("POST", path27, body);
7624
7649
  }
7625
- async function internalPut(path26, body) {
7626
- return client.request("PUT", path26, body);
7650
+ async function internalPut(path27, body) {
7651
+ return client.request("PUT", path27, body);
7627
7652
  }
7628
- async function internalDelete(path26) {
7629
- return client.request("DELETE", path26);
7653
+ async function internalDelete(path27) {
7654
+ return client.request("DELETE", path27);
7630
7655
  }
7631
- async function funnelRequest(method, path26, body) {
7656
+ async function funnelRequest(method, path27, body) {
7632
7657
  const headers = await client.buildHeaders();
7633
7658
  headers.Origin = "https://app.gohighlevel.com";
7634
7659
  headers.Referer = "https://app.gohighlevel.com/";
7635
- const url = `https://backend.leadconnectorhq.com/funnels${path26}`;
7660
+ const url = `https://backend.leadconnectorhq.com/funnels${path27}`;
7636
7661
  const options = { method, headers };
7637
7662
  if (body && (method === "POST" || method === "PUT")) {
7638
7663
  options.body = JSON.stringify(body);
@@ -7640,7 +7665,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
7640
7665
  const response = await fetch(url, options);
7641
7666
  if (!response.ok) {
7642
7667
  const text2 = await response.text();
7643
- throw new Error(`Funnel API Error ${response.status}: ${method} ${path26}
7668
+ throw new Error(`Funnel API Error ${response.status}: ${method} ${path27}
7644
7669
  ${text2}`);
7645
7670
  }
7646
7671
  const text = await response.text();
@@ -8624,12 +8649,12 @@ var init_website = __esm({
8624
8649
  function registerPageStudioTools(server2, builderClient) {
8625
8650
  const client = builderClient;
8626
8651
  if (!client) return;
8627
- async function funnelRequest(method, path26) {
8652
+ async function funnelRequest(method, path27) {
8628
8653
  const headers = await client.buildHeaders();
8629
8654
  headers.Origin = "https://app.gohighlevel.com";
8630
8655
  headers.Referer = "https://app.gohighlevel.com/";
8631
- const response = await fetch(`https://backend.leadconnectorhq.com/funnels${path26}`, { method, headers });
8632
- if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path26}
8656
+ const response = await fetch(`https://backend.leadconnectorhq.com/funnels${path27}`, { method, headers });
8657
+ if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path27}
8633
8658
  ${await response.text()}`);
8634
8659
  const text = await response.text();
8635
8660
  return text ? JSON.parse(text) : {};
@@ -9013,9 +9038,9 @@ function cleanFormDataForSave(formData) {
9013
9038
  const cleaned = dropRedundantCaseCollidingKeys(formData);
9014
9039
  return isJsonObject(cleaned) ? cleaned : formData;
9015
9040
  }
9016
- async function formApiRequest(client, method, path26, body) {
9041
+ async function formApiRequest(client, method, path27, body) {
9017
9042
  const headers = await client.buildHeaders();
9018
- const url = `https://backend.leadconnectorhq.com/forms${path26}`;
9043
+ const url = `https://backend.leadconnectorhq.com/forms${path27}`;
9019
9044
  const options = { method, headers };
9020
9045
  if (body && (method === "POST" || method === "PUT")) {
9021
9046
  options.body = JSON.stringify(body);
@@ -9023,7 +9048,7 @@ async function formApiRequest(client, method, path26, body) {
9023
9048
  const response = await fetch(url, options);
9024
9049
  if (!response.ok) {
9025
9050
  const text2 = await response.text();
9026
- throw new Error(`Form API Error ${response.status}: ${method} ${path26}
9051
+ throw new Error(`Form API Error ${response.status}: ${method} ${path27}
9027
9052
  ${text2}`);
9028
9053
  }
9029
9054
  const text = await response.text();
@@ -9037,7 +9062,7 @@ ${text2}`);
9037
9062
  function registerFormBuilderTools(server2, builderClient, publicClient) {
9038
9063
  const client = builderClient;
9039
9064
  if (!client) return;
9040
- const formRequest = (method, path26, body) => formApiRequest(client, method, path26, body);
9065
+ const formRequest = (method, path27, body) => formApiRequest(client, method, path27, body);
9041
9066
  server2.tool(
9042
9067
  "get_form_full",
9043
9068
  "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.",
@@ -9160,10 +9185,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
9160
9185
  },
9161
9186
  async ({ formId, limit, skip }) => {
9162
9187
  try {
9163
- let path26 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
9164
- if (formId) path26 += `&formId=${formId}`;
9165
- if (skip) path26 += `&skip=${skip}`;
9166
- const result = await formRequest("GET", path26);
9188
+ let path27 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
9189
+ if (formId) path27 += `&formId=${formId}`;
9190
+ if (skip) path27 += `&skip=${skip}`;
9191
+ const result = await formRequest("GET", path27);
9167
9192
  return {
9168
9193
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
9169
9194
  };
@@ -9570,9 +9595,9 @@ var init_funnel_qa = __esm({
9570
9595
  function registerPipelineBuilderTools(server2, builderClient) {
9571
9596
  const client = builderClient;
9572
9597
  if (!client) return;
9573
- async function pipelineRequest(method, path26, body) {
9598
+ async function pipelineRequest(method, path27, body) {
9574
9599
  const headers = await client.buildHeaders();
9575
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path26}`;
9600
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path27}`;
9576
9601
  const options = { method, headers };
9577
9602
  if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
9578
9603
  options.body = JSON.stringify(body);
@@ -9580,7 +9605,7 @@ function registerPipelineBuilderTools(server2, builderClient) {
9580
9605
  const response = await fetch(url, options);
9581
9606
  if (!response.ok) {
9582
9607
  const text2 = await response.text();
9583
- throw new Error(`Pipeline API Error ${response.status}: ${method} ${path26}
9608
+ throw new Error(`Pipeline API Error ${response.status}: ${method} ${path27}
9584
9609
  ${text2}`);
9585
9610
  }
9586
9611
  const text = await response.text();
@@ -12619,9 +12644,9 @@ function buildContactSmartListBody(args) {
12619
12644
  function registerSmartListTools(server2, builderClient) {
12620
12645
  const client = builderClient;
12621
12646
  if (!client) return;
12622
- async function smartListRequest(method, path26, body) {
12647
+ async function smartListRequest(method, path27, body) {
12623
12648
  const headers = await client.buildHeaders();
12624
- const url = `${SMARTLIST_BASE}${path26}`;
12649
+ const url = `${SMARTLIST_BASE}${path27}`;
12625
12650
  const options = { method, headers };
12626
12651
  if (body && (method === "POST" || method === "PUT")) {
12627
12652
  options.body = JSON.stringify(body);
@@ -12629,7 +12654,7 @@ function registerSmartListTools(server2, builderClient) {
12629
12654
  const response = await fetch(url, options);
12630
12655
  if (!response.ok) {
12631
12656
  const text2 = await response.text();
12632
- throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path26}
12657
+ throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path27}
12633
12658
  ${text2}`);
12634
12659
  }
12635
12660
  const text = await response.text();
@@ -12861,12 +12886,12 @@ var init_smart_lists = __esm({
12861
12886
  function registerReputationTools(server2, builderClient) {
12862
12887
  const client = builderClient;
12863
12888
  if (!client) return;
12864
- async function reputationRequest(method, path26) {
12889
+ async function reputationRequest(method, path27) {
12865
12890
  const headers = await client.buildHeaders();
12866
- const response = await fetch(`${REPUTATION_BASE}${path26}`, { method, headers });
12891
+ const response = await fetch(`${REPUTATION_BASE}${path27}`, { method, headers });
12867
12892
  if (!response.ok) {
12868
12893
  const text2 = await response.text();
12869
- throw new Error(`Reputation API Error ${response.status}: ${method} ${path26}
12894
+ throw new Error(`Reputation API Error ${response.status}: ${method} ${path27}
12870
12895
  ${text2}`);
12871
12896
  }
12872
12897
  const text = await response.text();
@@ -13007,16 +13032,16 @@ var init_email_campaigns = __esm({
13007
13032
  function registerMembershipTools(server2, builderClient) {
13008
13033
  const client = builderClient;
13009
13034
  if (!client) return;
13010
- async function membershipRequest(path26, method = "GET", body) {
13035
+ async function membershipRequest(path27, method = "GET", body) {
13011
13036
  const headers = await client.buildHeaders();
13012
- const response = await fetch(`${MEMBERSHIP_BASE2}${path26}`, {
13037
+ const response = await fetch(`${MEMBERSHIP_BASE2}${path27}`, {
13013
13038
  method,
13014
13039
  headers,
13015
13040
  body: body ? JSON.stringify(body) : void 0
13016
13041
  });
13017
13042
  if (!response.ok) {
13018
13043
  const text2 = await response.text();
13019
- throw new Error(`Membership API Error ${response.status}: ${method} ${path26}
13044
+ throw new Error(`Membership API Error ${response.status}: ${method} ${path27}
13020
13045
  ${text2}`);
13021
13046
  }
13022
13047
  const text = await response.text();
@@ -14761,10 +14786,10 @@ async function confirm(run) {
14761
14786
  async function runProbes(client, loc) {
14762
14787
  return Promise.all(
14763
14788
  PROBES.map((probe) => confirm(async () => {
14764
- const path26 = probe.path.replace("{loc}", loc);
14789
+ const path27 = probe.path.replace("{loc}", loc);
14765
14790
  const params = probe.params ? Object.fromEntries(Object.entries(probe.params).map(([k, v]) => [k, substitute(v, loc)])) : void 0;
14766
14791
  try {
14767
- const raw = await client.get(path26, { params, noRetry: true });
14792
+ const raw = await client.get(path27, { params, noRetry: true });
14768
14793
  const count = countRows(raw, probe.listKeys);
14769
14794
  return {
14770
14795
  key: probe.key,
@@ -16443,10 +16468,414 @@ var init_assessment = __esm({
16443
16468
  }
16444
16469
  });
16445
16470
 
16471
+ // src/command-os/connectors/store.ts
16472
+ function connectorsPath() {
16473
+ return path9.join(appDataDir(), "connectors.json");
16474
+ }
16475
+ function readFile() {
16476
+ try {
16477
+ const raw = fs10.readFileSync(connectorsPath(), "utf-8");
16478
+ const parsed = JSON.parse(raw);
16479
+ if (!parsed || typeof parsed !== "object" || !parsed.connectors) return { v: 1, connectors: {} };
16480
+ return parsed;
16481
+ } catch {
16482
+ return { v: 1, connectors: {} };
16483
+ }
16484
+ }
16485
+ function writeFile(f) {
16486
+ ensureAppDataDir();
16487
+ const target = connectorsPath();
16488
+ const tmp = `${target}.${process.pid}.tmp`;
16489
+ fs10.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", "utf-8");
16490
+ try {
16491
+ fs10.chmodSync(tmp, 384);
16492
+ } catch {
16493
+ }
16494
+ fs10.renameSync(tmp, target);
16495
+ try {
16496
+ fs10.chmodSync(target, 384);
16497
+ } catch {
16498
+ }
16499
+ }
16500
+ function connect(system, key, label, now = () => /* @__PURE__ */ new Date()) {
16501
+ const trimmed = String(key ?? "").trim();
16502
+ if (!trimmed) throw new Error("No key given. Nothing was stored.");
16503
+ const f = readFile();
16504
+ const secret = { system, key: trimmed, label, connectedAt: now().toISOString() };
16505
+ f.connectors[system] = secret;
16506
+ writeFile(f);
16507
+ return summaryOf(secret);
16508
+ }
16509
+ function disconnect(system) {
16510
+ const f = readFile();
16511
+ if (!f.connectors[system]) return false;
16512
+ delete f.connectors[system];
16513
+ writeFile(f);
16514
+ return true;
16515
+ }
16516
+ function summaryOf(s) {
16517
+ return { system: s.system, label: s.label, connectedAt: s.connectedAt, hint: hintOf(s.key) };
16518
+ }
16519
+ function listConnectors() {
16520
+ return Object.values(readFile().connectors).filter((s) => Boolean(s)).map(summaryOf);
16521
+ }
16522
+ function isConnected(system) {
16523
+ return Boolean(readFile().connectors[system]);
16524
+ }
16525
+ async function withKey(system, fn) {
16526
+ const s = readFile().connectors[system];
16527
+ if (!s) throw new Error(`${system} is not connected on this machine.`);
16528
+ return fn(s.key);
16529
+ }
16530
+ var fs10, path9, hintOf;
16531
+ var init_store2 = __esm({
16532
+ "src/command-os/connectors/store.ts"() {
16533
+ "use strict";
16534
+ fs10 = __toESM(require("node:fs"));
16535
+ path9 = __toESM(require("node:path"));
16536
+ init_credentials_store();
16537
+ hintOf = (key) => `\u2026${String(key).slice(-4)}`;
16538
+ }
16539
+ });
16540
+
16541
+ // src/command-os/connectors/stripe.ts
16542
+ function identity(kind, externalId) {
16543
+ return { system: SYSTEM, kind, externalId, tenantId: null };
16544
+ }
16545
+ function observe(id, field, value, observedAt, raw) {
16546
+ return { identity: id, field, value, source: SYSTEM, observedAt, raw };
16547
+ }
16548
+ function mapSubscription(sub, observedAt) {
16549
+ const id = identity("client", sub.customer);
16550
+ const price = sub.items?.data?.[0]?.price;
16551
+ const amount = fromMinor(price?.unit_amount ?? null);
16552
+ const o = [
16553
+ observe(id, "stripe_subscription_id", sub.id, observedAt, sub),
16554
+ observe(id, "subscription_status", sub.status, observedAt, sub)
16555
+ ];
16556
+ if (amount !== null) o.push(observe(id, "pays_per_period", amount, observedAt, sub));
16557
+ if (price?.recurring?.interval) o.push(observe(id, "billing_interval", price.recurring.interval, observedAt, sub));
16558
+ const renews = iso(sub.current_period_end);
16559
+ if (renews) o.push(observe(id, "renews_at", renews, observedAt, sub));
16560
+ o.push(observe(id, "cancelling_at_period_end", sub.cancel_at_period_end === true, observedAt, sub));
16561
+ return { identities: [id], observations: o };
16562
+ }
16563
+ function mapInvoice(inv, observedAt) {
16564
+ const id = identity("payment", inv.id);
16565
+ const due = iso(inv.due_date ?? null);
16566
+ const o = [
16567
+ observe(id, "invoice_status", inv.status, observedAt, inv),
16568
+ observe(id, "stripe_customer_id", inv.customer, observedAt, inv)
16569
+ ];
16570
+ const remaining = fromMinor(inv.amount_remaining ?? inv.amount_due ?? null);
16571
+ if (remaining !== null) o.push(observe(id, "amount_outstanding", remaining, observedAt, inv));
16572
+ if (due) o.push(observe(id, "due_at", due, observedAt, inv));
16573
+ if (inv.number) o.push(observe(id, "invoice_number", inv.number, observedAt, inv));
16574
+ if (inv.hosted_invoice_url) o.push(observe(id, "invoice_url", inv.hosted_invoice_url, observedAt, inv));
16575
+ return { identities: [id], observations: o };
16576
+ }
16577
+ function proposeClientLinks(customers, known, observedAt) {
16578
+ const byEmail = /* @__PURE__ */ new Map();
16579
+ for (const k of known) {
16580
+ for (const e of k.emails) {
16581
+ const key = e.trim().toLowerCase();
16582
+ if (!key) continue;
16583
+ byEmail.set(key, [...byEmail.get(key) ?? [], k]);
16584
+ }
16585
+ }
16586
+ const links = [];
16587
+ for (const c of customers) {
16588
+ const email = (c.email ?? "").trim().toLowerCase();
16589
+ if (!email) continue;
16590
+ const hits = byEmail.get(email) ?? [];
16591
+ if (hits.length !== 1) continue;
16592
+ const k = hits[0];
16593
+ links.push({
16594
+ a: identity("client", c.id),
16595
+ b: { system: "ghl", kind: "client", externalId: k.tenantId, tenantId: k.tenantId },
16596
+ confidence: "weak",
16597
+ reason: `Stripe billing email ${email} matches ${k.name}. Confirm before this counts.`,
16598
+ observedAt
16599
+ });
16600
+ }
16601
+ return links;
16602
+ }
16603
+ function outstanding(invoices, now = /* @__PURE__ */ new Date()) {
16604
+ return invoices.filter((i) => i.status === "open" || i.status === "uncollectible").map((i) => {
16605
+ const dueAt = iso(i.due_date ?? null);
16606
+ const days = dueAt ? Math.floor((now.getTime() - new Date(dueAt).getTime()) / 864e5) : null;
16607
+ return {
16608
+ invoiceId: i.id,
16609
+ number: i.number ?? null,
16610
+ customerId: i.customer,
16611
+ amount: fromMinor(i.amount_remaining ?? i.amount_due ?? null) ?? 0,
16612
+ dueAt,
16613
+ daysOverdue: days !== null && days > 0 ? days : null,
16614
+ url: i.hosted_invoice_url ?? null
16615
+ };
16616
+ }).sort((a, b) => (b.daysOverdue ?? -1) - (a.daysOverdue ?? -1));
16617
+ }
16618
+ function renewalsDue(subs, withinDays = 30, now = /* @__PURE__ */ new Date()) {
16619
+ const out = [];
16620
+ for (const s of subs) {
16621
+ if (s.status !== "active" && s.status !== "trialing" && s.status !== "past_due") continue;
16622
+ const renewsAt = iso(s.current_period_end);
16623
+ if (!renewsAt) continue;
16624
+ const days = Math.ceil((new Date(renewsAt).getTime() - now.getTime()) / 864e5);
16625
+ if (days > withinDays) continue;
16626
+ out.push({
16627
+ customerId: s.customer,
16628
+ renewsAt,
16629
+ days,
16630
+ cancelling: s.cancel_at_period_end === true,
16631
+ amount: fromMinor(s.items?.data?.[0]?.price?.unit_amount ?? null)
16632
+ });
16633
+ }
16634
+ return out.sort((a, b) => a.days - b.days);
16635
+ }
16636
+ async function health(fetcher, now = () => /* @__PURE__ */ new Date()) {
16637
+ const checkedAt = now().toISOString();
16638
+ try {
16639
+ const res = await fetcher("/v1/subscriptions", { limit: "1" });
16640
+ if (!res || !Array.isArray(res.data)) {
16641
+ return { system: SYSTEM, state: "INCONCLUSIVE", detail: "Stripe answered, but not with anything we recognised.", checkedAt };
16642
+ }
16643
+ return { system: SYSTEM, state: "WORKING", detail: "Reading subscriptions and invoices.", checkedAt };
16644
+ } catch (e) {
16645
+ const msg3 = String(e?.message ?? e);
16646
+ if (/401|403|permission|invalid api key/i.test(msg3)) {
16647
+ return { system: SYSTEM, state: "NO-ACCESS", detail: "Stripe refused the key. Check it allows reading subscriptions and invoices.", checkedAt };
16648
+ }
16649
+ if (/network|timeout|ENOTFOUND|ECONN/i.test(msg3)) {
16650
+ return { system: SYSTEM, state: "UNAVAILABLE", detail: "Could not reach Stripe. Nothing to fix on your side.", checkedAt };
16651
+ }
16652
+ return { system: SYSTEM, state: "BROKEN", detail: `Stripe read failed: ${msg3.slice(0, 120)}`, checkedAt };
16653
+ }
16654
+ }
16655
+ var SYSTEM, iso, fromMinor;
16656
+ var init_stripe = __esm({
16657
+ "src/command-os/connectors/stripe.ts"() {
16658
+ "use strict";
16659
+ SYSTEM = "stripe";
16660
+ iso = (unix) => typeof unix === "number" && isFinite(unix) ? new Date(unix * 1e3).toISOString() : null;
16661
+ fromMinor = (n) => typeof n === "number" && isFinite(n) ? Math.round(n) / 100 : null;
16662
+ }
16663
+ });
16664
+
16665
+ // src/command-os/connectors/spine.ts
16666
+ function identityKey(i) {
16667
+ return `${i.system}:${i.kind}:${i.externalId}`;
16668
+ }
16669
+ function linkIsLive(l) {
16670
+ return !l.retractedAt;
16671
+ }
16672
+ function resolveEntities(identities, links) {
16673
+ const parent = /* @__PURE__ */ new Map();
16674
+ const byKey = /* @__PURE__ */ new Map();
16675
+ for (const i of identities) {
16676
+ parent.set(identityKey(i), identityKey(i));
16677
+ byKey.set(identityKey(i), i);
16678
+ }
16679
+ const find = (k) => {
16680
+ let cur = k;
16681
+ while (parent.get(cur) !== cur) {
16682
+ const up = parent.get(cur);
16683
+ if (up === void 0) return cur;
16684
+ parent.set(cur, parent.get(up) ?? up);
16685
+ cur = parent.get(cur);
16686
+ }
16687
+ return cur;
16688
+ };
16689
+ const union = (x, y) => {
16690
+ const rx = find(x), ry = find(y);
16691
+ if (rx !== ry) parent.set(rx, ry);
16692
+ };
16693
+ for (const l of links) {
16694
+ if (!linkIsLive(l)) continue;
16695
+ const ka = identityKey(l.a), kb = identityKey(l.b);
16696
+ if (!parent.has(ka) || !parent.has(kb)) continue;
16697
+ union(ka, kb);
16698
+ }
16699
+ const groups = /* @__PURE__ */ new Map();
16700
+ for (const k of parent.keys()) {
16701
+ const root = find(k);
16702
+ const bucket = groups.get(root) ?? [];
16703
+ bucket.push(byKey.get(k));
16704
+ groups.set(root, bucket);
16705
+ }
16706
+ return [...groups.values()];
16707
+ }
16708
+ function resolveTenant(group, links) {
16709
+ const direct = group.filter((i) => i.tenantId !== null);
16710
+ const distinct = [...new Set(direct.map((i) => i.tenantId))];
16711
+ if (distinct.length === 1) {
16712
+ return { tenantId: distinct[0], because: `${direct[0].system} says so directly` };
16713
+ }
16714
+ if (distinct.length > 1) {
16715
+ const keys = new Set(group.map(identityKey));
16716
+ const touching = links.filter(linkIsLive).filter((l) => keys.has(identityKey(l.a)) || keys.has(identityKey(l.b)));
16717
+ let best = null;
16718
+ let tied = false;
16719
+ for (const l of touching) {
16720
+ const t = l.a.tenantId ?? l.b.tenantId;
16721
+ if (t === null || t === void 0) continue;
16722
+ if (!best) {
16723
+ best = l;
16724
+ tied = false;
16725
+ continue;
16726
+ }
16727
+ const r = CONFIDENCE_RANK[l.confidence] - CONFIDENCE_RANK[best.confidence];
16728
+ if (r > 0) {
16729
+ best = l;
16730
+ tied = false;
16731
+ } else if (r === 0 && (best.a.tenantId ?? best.b.tenantId) !== t) tied = true;
16732
+ }
16733
+ if (best && !tied) {
16734
+ const t = best.a.tenantId ?? best.b.tenantId;
16735
+ return { tenantId: t, because: `${best.confidence} link: ${best.reason}` };
16736
+ }
16737
+ return {
16738
+ tenantId: null,
16739
+ because: `systems disagree about which client this belongs to (${distinct.join(", ")}) and no link settles it`
16740
+ };
16741
+ }
16742
+ return {
16743
+ tenantId: null,
16744
+ because: "no system on this record knows which client it belongs to"
16745
+ };
16746
+ }
16747
+ function currentValue(observations, field, sourcePriority = []) {
16748
+ const hits = observations.filter((o) => o.field === field);
16749
+ if (!hits.length) return null;
16750
+ const rank = (s) => {
16751
+ const i = sourcePriority.indexOf(s);
16752
+ return i === -1 ? sourcePriority.length : i;
16753
+ };
16754
+ const best = hits.reduce((a, b) => {
16755
+ if (a.observedAt !== b.observedAt) return a.observedAt > b.observedAt ? a : b;
16756
+ return rank(a.source) <= rank(b.source) ? a : b;
16757
+ });
16758
+ return { value: best.value, source: best.source, observedAt: best.observedAt };
16759
+ }
16760
+ var CONFIDENCE_RANK;
16761
+ var init_spine = __esm({
16762
+ "src/command-os/connectors/spine.ts"() {
16763
+ "use strict";
16764
+ CONFIDENCE_RANK = { asserted: 3, strong: 2, weak: 1 };
16765
+ }
16766
+ });
16767
+
16768
+ // src/command-os/connectors/to-engagements.ts
16769
+ function toLocalDate(iso2) {
16770
+ if (typeof iso2 !== "string") return void 0;
16771
+ const d = new Date(iso2);
16772
+ if (Number.isNaN(d.getTime())) return void 0;
16773
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
16774
+ }
16775
+ function statusFrom(sub) {
16776
+ switch (sub) {
16777
+ case "active":
16778
+ case "trialing":
16779
+ return "active";
16780
+ case "past_due":
16781
+ case "unpaid":
16782
+ return "active";
16783
+ // still a client, and the money problem is its own line
16784
+ case "canceled":
16785
+ return "ended";
16786
+ default:
16787
+ return void 0;
16788
+ }
16789
+ }
16790
+ function bridgeToEngagements(identities, links, observations, existing = {}) {
16791
+ const trusted = links.filter((l) => linkIsLive(l) && TRUSTED.has(l.confidence));
16792
+ const awaitingConfirmation = links.filter((l) => linkIsLive(l) && !TRUSTED.has(l.confidence));
16793
+ const patch = {};
16794
+ const disagreements = [];
16795
+ const unattributed = [];
16796
+ for (const group of resolveEntities(identities, trusted)) {
16797
+ const stripeOnes = group.filter((i) => i.system === "stripe");
16798
+ if (!stripeOnes.length) continue;
16799
+ const { tenantId, because } = resolveTenant(group, trusted);
16800
+ if (!tenantId) {
16801
+ for (const i of stripeOnes) unattributed.push({ identity: i, because });
16802
+ continue;
16803
+ }
16804
+ const keys = new Set(group.map((i) => `${i.system}:${i.kind}:${i.externalId}`));
16805
+ const mine = observations.filter((o) => keys.has(`${o.identity.system}:${o.identity.kind}:${o.identity.externalId}`));
16806
+ const was = existing[tenantId] ?? {};
16807
+ const into = patch[tenantId] ?? {};
16808
+ const pays = currentValue(mine, "pays_per_period")?.value;
16809
+ if (typeof pays === "number") {
16810
+ if (isSet(was.price) && was.price !== pays) {
16811
+ disagreements.push({ tenantId, field: "price", yours: was.price, stripe: pays });
16812
+ } else if (!isSet(was.price)) {
16813
+ into.price = pays;
16814
+ }
16815
+ }
16816
+ const interval = currentValue(mine, "billing_interval")?.value;
16817
+ if (!isSet(was.cadence) && (interval === "month" || interval === "year")) {
16818
+ if (interval === "month") into.cadence = "monthly";
16819
+ }
16820
+ const renews = toLocalDate(currentValue(mine, "renews_at")?.value);
16821
+ if (renews) {
16822
+ if (isSet(was.renewsOn) && was.renewsOn !== renews) {
16823
+ disagreements.push({ tenantId, field: "renewsOn", yours: was.renewsOn, stripe: renews });
16824
+ } else if (!isSet(was.renewsOn)) {
16825
+ into.renewsOn = renews;
16826
+ }
16827
+ }
16828
+ const status = statusFrom(currentValue(mine, "subscription_status")?.value);
16829
+ if (status && !isSet(was.status)) into.status = status;
16830
+ if (Object.keys(into).length) patch[tenantId] = into;
16831
+ }
16832
+ return { patch, disagreements, unattributed, awaitingConfirmation };
16833
+ }
16834
+ function owedByClient(owing, identities, links, nameFor2) {
16835
+ const trusted = links.filter((l) => linkIsLive(l) && TRUSTED.has(l.confidence));
16836
+ const tenantOfCustomer = /* @__PURE__ */ new Map();
16837
+ for (const group of resolveEntities(identities, trusted)) {
16838
+ const { tenantId } = resolveTenant(group, trusted);
16839
+ if (!tenantId) continue;
16840
+ for (const i of group) {
16841
+ if (i.system === "stripe" && i.kind === "client") tenantOfCustomer.set(i.externalId, tenantId);
16842
+ }
16843
+ }
16844
+ const lines = [];
16845
+ let unattributedTotal = 0;
16846
+ let unattributedCount = 0;
16847
+ for (const o of owing) {
16848
+ const tenant = tenantOfCustomer.get(o.customerId);
16849
+ if (!tenant) {
16850
+ unattributedTotal += o.amount;
16851
+ unattributedCount += 1;
16852
+ continue;
16853
+ }
16854
+ lines.push({
16855
+ client: nameFor2(tenant),
16856
+ amount: o.amount,
16857
+ number: o.number,
16858
+ daysOverdue: o.daysOverdue,
16859
+ url: o.url
16860
+ });
16861
+ }
16862
+ lines.sort((a, b) => (b.daysOverdue ?? -1) - (a.daysOverdue ?? -1));
16863
+ return { lines, unattributedTotal, unattributedCount };
16864
+ }
16865
+ var TRUSTED, isSet;
16866
+ var init_to_engagements = __esm({
16867
+ "src/command-os/connectors/to-engagements.ts"() {
16868
+ "use strict";
16869
+ init_spine();
16870
+ TRUSTED = /* @__PURE__ */ new Set(["asserted", "strong"]);
16871
+ isSet = (v) => v !== void 0 && v !== null && v !== "";
16872
+ }
16873
+ });
16874
+
16446
16875
  // src/client-engagements.ts
16447
16876
  function clientEngagementsPath() {
16448
16877
  const override = process.env[CLIENT_ENGAGEMENTS_ENV];
16449
- return override && override.trim() ? override : path9.join(appDataDir(), "client-engagements.json");
16878
+ return override && override.trim() ? override : path10.join(appDataDir(), "client-engagements.json");
16450
16879
  }
16451
16880
  function safeParse2(raw) {
16452
16881
  try {
@@ -16490,7 +16919,7 @@ function sanitizeEngagements(raw) {
16490
16919
  }
16491
16920
  function readClientEngagements(file = clientEngagementsPath()) {
16492
16921
  try {
16493
- const raw = JSON.parse(fs10.readFileSync(file, "utf8"));
16922
+ const raw = JSON.parse(fs11.readFileSync(file, "utf8"));
16494
16923
  if (raw && typeof raw === "object") return sanitizeEngagements(raw);
16495
16924
  } catch {
16496
16925
  }
@@ -16517,7 +16946,7 @@ function writeClientEngagement(locationId2, patch, file = clientEngagementsPath(
16517
16946
  if (!id || FORBIDDEN_KEY.has(id)) throw new Error("A client id is required.");
16518
16947
  const stamp = () => {
16519
16948
  try {
16520
- return fs10.readFileSync(file, "utf8");
16949
+ return fs11.readFileSync(file, "utf8");
16521
16950
  } catch {
16522
16951
  return "";
16523
16952
  }
@@ -16537,20 +16966,20 @@ function writeClientEngagement(locationId2, patch, file = clientEngagementsPath(
16537
16966
  };
16538
16967
  const persist = (next) => {
16539
16968
  if (file === clientEngagementsPath() && !process.env[CLIENT_ENGAGEMENTS_ENV]) ensureAppDataDir();
16540
- fs10.mkdirSync(path9.dirname(file), { recursive: true });
16969
+ fs11.mkdirSync(path10.dirname(file), { recursive: true });
16541
16970
  const tmp = `${file}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
16542
16971
  try {
16543
- fs10.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
16972
+ fs11.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
16544
16973
  if (process.platform !== "win32") {
16545
16974
  try {
16546
- fs10.chmodSync(tmp, 384);
16975
+ fs11.chmodSync(tmp, 384);
16547
16976
  } catch {
16548
16977
  }
16549
16978
  }
16550
- fs10.renameSync(tmp, file);
16979
+ fs11.renameSync(tmp, file);
16551
16980
  } catch (e) {
16552
16981
  try {
16553
- fs10.unlinkSync(tmp);
16982
+ fs11.unlinkSync(tmp);
16554
16983
  } catch {
16555
16984
  }
16556
16985
  throw e;
@@ -16609,12 +17038,12 @@ function monthlyFloor(roster, profile) {
16609
17038
  counted: priced.length
16610
17039
  };
16611
17040
  }
16612
- var fs10, path9, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
17041
+ var fs11, path10, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
16613
17042
  var init_client_engagements = __esm({
16614
17043
  "src/client-engagements.ts"() {
16615
17044
  "use strict";
16616
- fs10 = __toESM(require("fs"));
16617
- path9 = __toESM(require("path"));
17045
+ fs11 = __toESM(require("fs"));
17046
+ path10 = __toESM(require("path"));
16618
17047
  init_credentials_store();
16619
17048
  init_field_sanitize();
16620
17049
  CLIENT_ENGAGEMENTS_ENV = "GHL_MCP_CLIENT_ENGAGEMENTS";
@@ -16627,6 +17056,182 @@ var init_client_engagements = __esm({
16627
17056
  }
16628
17057
  });
16629
17058
 
17059
+ // src/tools/connectors.ts
17060
+ function stripeFetcher(key) {
17061
+ return async (path27, query) => {
17062
+ const url = new URL(`https://api.stripe.com${path27}`);
17063
+ for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
17064
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
17065
+ if (!res.ok) {
17066
+ throw new Error(`${res.status} ${res.statusText}`);
17067
+ }
17068
+ return res.json();
17069
+ };
17070
+ }
17071
+ async function listAll(f, path27, extra = {}) {
17072
+ const out = [];
17073
+ let after;
17074
+ for (let page = 0; page < 20; page++) {
17075
+ const q2 = { limit: "100", ...extra };
17076
+ if (after) q2.starting_after = after;
17077
+ const res = await f(path27, q2);
17078
+ const data = res?.data ?? [];
17079
+ out.push(...data);
17080
+ if (!res?.has_more || !data.length) break;
17081
+ after = data[data.length - 1]?.id;
17082
+ if (!after) break;
17083
+ }
17084
+ return out;
17085
+ }
17086
+ function registerConnectorTools(server2) {
17087
+ safeTool(
17088
+ server2,
17089
+ "connect_system",
17090
+ "Store a connector's credentials on THIS machine so Command OS can read that system. The key is written next to your GoHighLevel credentials, readable only by you, and is never sent anywhere, never returned by any tool, and never written to a log. Today: Stripe, which answers who owes you money, what each client pays and when they renew.",
17091
+ {
17092
+ system: import_zod57.z.enum(["stripe"]).describe("The system to connect. Stripe is the one that is built."),
17093
+ key: import_zod57.z.string().min(8).describe("A Stripe RESTRICTED key with read access to subscriptions, invoices and customers. A restricted key is deliberate: nothing here writes, so nothing here needs a key that can."),
17094
+ label: import_zod57.z.string().optional().describe("Which account this is, so you can tell two apart later.")
17095
+ },
17096
+ async ({ system, key, label }) => {
17097
+ const s = connect(system, key, label);
17098
+ return {
17099
+ connected: s.system,
17100
+ label: s.label ?? null,
17101
+ keyEndsWith: s.hint,
17102
+ storedAt: "your own machine, alongside your GoHighLevel credentials",
17103
+ nextStep: "Run sync_stripe to see what it finds. It changes nothing until you tell it to."
17104
+ };
17105
+ }
17106
+ );
17107
+ safeTool(
17108
+ server2,
17109
+ "list_connected_systems",
17110
+ "Which systems Command OS can read on this machine, and when each was connected. Never shows a key.",
17111
+ {},
17112
+ async () => {
17113
+ const all = listConnectors();
17114
+ return {
17115
+ connected: all,
17116
+ note: all.length ? null : "Nothing is connected yet. The daily brief can only see GoHighLevel."
17117
+ };
17118
+ }
17119
+ );
17120
+ safeTool(
17121
+ server2,
17122
+ "disconnect_system",
17123
+ "Remove a connector's credentials from this machine. The data already written into your client records stays; only the ability to read that system again is removed.",
17124
+ { system: import_zod57.z.enum(["stripe"]) },
17125
+ async ({ system }) => ({
17126
+ removed: disconnect(system),
17127
+ note: "Anything already written into your client records is untouched."
17128
+ })
17129
+ );
17130
+ safeTool(
17131
+ server2,
17132
+ "sync_stripe",
17133
+ "Read your Stripe and fill in what your client records do not know: what each client pays, when they renew, and who owes you money. DRY RUN BY DEFAULT \u2014 it shows you every change before making any. A client whose Stripe customer cannot be matched to one of your clients is reported, never guessed at.",
17134
+ {
17135
+ apply: import_zod57.z.boolean().optional().describe("Write the changes into your client records. Defaults to false: look first."),
17136
+ confirmLinks: import_zod57.z.array(import_zod57.z.object({
17137
+ stripeCustomerId: import_zod57.z.string(),
17138
+ clientKey: import_zod57.z.string().describe("The client in your records this Stripe customer is.")
17139
+ })).optional().describe("Matches you are confirming. An email match alone is never enough to move money against a client's name.")
17140
+ },
17141
+ async ({ apply, confirmLinks }) => {
17142
+ if (!isConnected("stripe")) {
17143
+ return { error: "Stripe is not connected on this machine. Run connect_system first." };
17144
+ }
17145
+ const observedAt = (/* @__PURE__ */ new Date()).toISOString();
17146
+ const record = readClientEngagements();
17147
+ const clients = record?.clients ?? {};
17148
+ const known = Object.entries(clients).map(([key, c]) => ({
17149
+ tenantId: key,
17150
+ name: c.clientName ?? key,
17151
+ emails: []
17152
+ // Layer 1 holds no billing email yet; matches come from confirmations below.
17153
+ }));
17154
+ const result = await withKey("stripe", async (key) => {
17155
+ const f = stripeFetcher(key);
17156
+ const h = await health(f);
17157
+ if (h.state !== "WORKING") return { health: h, subs: [], invoices: [], customers: [] };
17158
+ const [subs, invoices, customers] = await Promise.all([
17159
+ listAll(f, "/v1/subscriptions", { status: "all" }),
17160
+ listAll(f, "/v1/invoices"),
17161
+ listAll(f, "/v1/customers")
17162
+ ]);
17163
+ return { health: h, subs, invoices, customers };
17164
+ });
17165
+ if (result.health.state !== "WORKING") {
17166
+ return { health: result.health, readNothing: true };
17167
+ }
17168
+ const identities = [];
17169
+ const observations = [];
17170
+ for (const s of result.subs) {
17171
+ const m = mapSubscription(s, observedAt);
17172
+ identities.push(...m.identities);
17173
+ observations.push(...m.observations);
17174
+ }
17175
+ for (const i of result.invoices) {
17176
+ const m = mapInvoice(i, observedAt);
17177
+ identities.push(...m.identities);
17178
+ observations.push(...m.observations);
17179
+ }
17180
+ for (const key of Object.keys(clients)) {
17181
+ identities.push({ system: "ghl", kind: "client", externalId: key, tenantId: key });
17182
+ }
17183
+ const links = (confirmLinks ?? []).map((c) => ({
17184
+ a: { system: "stripe", kind: "client", externalId: c.stripeCustomerId, tenantId: null },
17185
+ b: { system: "ghl", kind: "client", externalId: c.clientKey, tenantId: c.clientKey },
17186
+ confidence: "asserted",
17187
+ reason: "you confirmed this match",
17188
+ observedAt
17189
+ }));
17190
+ links.push(...proposeClientLinks(result.customers, known, observedAt));
17191
+ const bridged = bridgeToEngagements(identities, links, observations, clients);
17192
+ const owed = owedByClient(
17193
+ outstanding(result.invoices),
17194
+ identities,
17195
+ links,
17196
+ (t) => clients[t]?.clientName ?? t
17197
+ );
17198
+ const renewals = renewalsDue(result.subs);
17199
+ let written = 0;
17200
+ if (apply) {
17201
+ for (const [clientKey, p] of Object.entries(bridged.patch)) {
17202
+ writeClientEngagement(clientKey, p);
17203
+ written += 1;
17204
+ }
17205
+ }
17206
+ const unmatched = result.customers.filter((c) => !links.some((l) => l.a.externalId === c.id && l.confidence === "asserted")).map((c) => ({ stripeCustomerId: c.id, email: c.email ?? null, name: c.name ?? null }));
17207
+ return {
17208
+ health: result.health,
17209
+ read: { subscriptions: result.subs.length, invoices: result.invoices.length, customers: result.customers.length },
17210
+ wouldChange: bridged.patch,
17211
+ written: written > 0 ? `${written} client record(s) updated` : false,
17212
+ owed: owed.lines,
17213
+ owedYouCannotName: owed.unattributedCount ? { count: owed.unattributedCount, total: owed.unattributedTotal } : null,
17214
+ renewalsInside30Days: renewals,
17215
+ disagreements: bridged.disagreements,
17216
+ needsYourConfirmation: unmatched,
17217
+ note: written > 0 ? "Written. Tomorrow's brief will know what each of these clients pays and when they renew." : "Nothing was changed. Re-run with apply:true once the matches above are right."
17218
+ };
17219
+ }
17220
+ );
17221
+ }
17222
+ var import_zod57;
17223
+ var init_connectors = __esm({
17224
+ "src/tools/connectors.ts"() {
17225
+ "use strict";
17226
+ import_zod57 = require("zod");
17227
+ init_tool_helpers();
17228
+ init_store2();
17229
+ init_stripe();
17230
+ init_to_engagements();
17231
+ init_client_engagements();
17232
+ }
17233
+ });
17234
+
16630
17235
  // src/client-week.ts
16631
17236
  function daysUntil(then, now) {
16632
17237
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(then);
@@ -16873,17 +17478,17 @@ var init_stage_boundary = __esm({
16873
17478
  // src/plan-guide.ts
16874
17479
  function skillRoot() {
16875
17480
  const candidates = [
16876
- path10.join(__dirname, "..", "skills", "blueprint"),
17481
+ path11.join(__dirname, "..", "skills", "blueprint"),
16877
17482
  // dist/ → package root
16878
- path10.join(__dirname, "..", "..", "skills", "blueprint"),
17483
+ path11.join(__dirname, "..", "..", "skills", "blueprint"),
16879
17484
  // src/ in dev
16880
- path10.join(process.cwd(), "skills", "blueprint")
17485
+ path11.join(process.cwd(), "skills", "blueprint")
16881
17486
  ];
16882
- return candidates.find((c) => fs11.existsSync(path10.join(c, "references", "build-plan-schema.md"))) ?? null;
17487
+ return candidates.find((c) => fs12.existsSync(path11.join(c, "references", "build-plan-schema.md"))) ?? null;
16883
17488
  }
16884
17489
  function readIf(p, max) {
16885
17490
  try {
16886
- const t = fs11.readFileSync(p, "utf8");
17491
+ const t = fs12.readFileSync(p, "utf8");
16887
17492
  return t.length > max ? t.slice(0, max) : t;
16888
17493
  } catch {
16889
17494
  return null;
@@ -16896,12 +17501,12 @@ function presetForIndustry(industry) {
16896
17501
  function loadPlanGuide(industry) {
16897
17502
  const root = skillRoot();
16898
17503
  if (!root) return "";
16899
- const schema = readIf(path10.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
17504
+ const schema = readIf(path11.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
16900
17505
  const presetName = presetForIndustry(industry);
16901
- const preset = readIf(path10.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
17506
+ const preset = readIf(path11.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
16902
17507
  const exampleFile = EXAMPLE_FOR_PRESET[presetName] ?? "sample-build-plan.json";
16903
- const example = readIf(path10.join(root, "examples", exampleFile), CAP_EXAMPLE);
16904
- const copyGuide = readIf(path10.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
17508
+ const example = readIf(path11.join(root, "examples", exampleFile), CAP_EXAMPLE);
17509
+ const copyGuide = readIf(path11.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
16905
17510
  if (!schema) return "";
16906
17511
  return `
16907
17512
  PLAN GUIDE \u2014 everything you need to write the build plan is below. Do NOT try to read files; you cannot.
@@ -16919,12 +17524,12 @@ ${example}
16919
17524
  ` : "") + `=== End of plan guide ===
16920
17525
  `;
16921
17526
  }
16922
- var fs11, path10, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
17527
+ var fs12, path11, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
16923
17528
  var init_plan_guide = __esm({
16924
17529
  "src/plan-guide.ts"() {
16925
17530
  "use strict";
16926
- fs11 = __toESM(require("fs"));
16927
- path10 = __toESM(require("path"));
17531
+ fs12 = __toESM(require("fs"));
17532
+ path11 = __toESM(require("path"));
16928
17533
  PRESET_FOR_INDUSTRY = {
16929
17534
  "med-spa": "med-spa",
16930
17535
  clinic: "clinic",
@@ -17480,7 +18085,7 @@ var init_question_set = __esm({
17480
18085
 
17481
18086
  // src/intake-to-build/plan.ts
17482
18087
  function nsRef(ns) {
17483
- return import_zod57.z.string().regex(new RegExp(`^${ns}\\.[a-z0-9]+(_[a-z0-9]+)*$`), `must be a ${ns}.* ref`);
18088
+ return import_zod58.z.string().regex(new RegExp(`^${ns}\\.[a-z0-9]+(_[a-z0-9]+)*$`), `must be a ${ns}.* ref`);
17484
18089
  }
17485
18090
  function refNamespace(ref) {
17486
18091
  return ref.split(".")[0];
@@ -17890,11 +18495,11 @@ function validateBuildPlan(input) {
17890
18495
  referencesScanned: scanned
17891
18496
  };
17892
18497
  }
17893
- var import_zod57, REF_NAMESPACES, REF_RE, refSchema, USER_PENDING_REF, userRefSchema, userRefOrPendingSchema, USER_ROLES, EMAIL_RE, userSchema, stageSchema, pipelineSchema, GHL_FIELD_DATATYPES, customFieldSchema, tagSchema, customValueSchema, CALENDAR_TYPES, TEAMLESS_CALENDAR_TYPES, SINGLE_STAFF_CALENDAR_TYPE, openHoursBlockSchema, CALENDAR_SLOT_UNITS, calendarSchema, formFieldSchema, formSchema, pageSchema, FUNNEL_TARGETS, FUNNEL_HOSTS, funnelSchema, emailAssetSchema, smsAssetSchema, emailTemplateSchema, smsTemplateSchema, templatesSchema, waitUnit, branchActionOptions, branchActionSchema, findOpportunitySchema, actionSchema, APPOINTMENT_STATUSES, CALL_STATUSES, NUMBER_VALIDATION_STATES, triggerSchema, workflowSchema, HANDOFF_OWNER_LEGACY, handoffSchema, buildPlanSchema, PLAN_ERROR_CODES, NURTURE_MIN_DAYS, WAIT_UNIT_DAYS, isNurtureName, isSpeedName, KNOWN_STANDARD_FORM_KEYS;
18498
+ var import_zod58, REF_NAMESPACES, REF_RE, refSchema, USER_PENDING_REF, userRefSchema, userRefOrPendingSchema, USER_ROLES, EMAIL_RE, userSchema, stageSchema, pipelineSchema, GHL_FIELD_DATATYPES, customFieldSchema, tagSchema, customValueSchema, CALENDAR_TYPES, TEAMLESS_CALENDAR_TYPES, SINGLE_STAFF_CALENDAR_TYPE, openHoursBlockSchema, CALENDAR_SLOT_UNITS, calendarSchema, formFieldSchema, formSchema, pageSchema, FUNNEL_TARGETS, FUNNEL_HOSTS, funnelSchema, emailAssetSchema, smsAssetSchema, emailTemplateSchema, smsTemplateSchema, templatesSchema, waitUnit, branchActionOptions, branchActionSchema, findOpportunitySchema, actionSchema, APPOINTMENT_STATUSES, CALL_STATUSES, NUMBER_VALIDATION_STATES, triggerSchema, workflowSchema, HANDOFF_OWNER_LEGACY, handoffSchema, buildPlanSchema, PLAN_ERROR_CODES, NURTURE_MIN_DAYS, WAIT_UNIT_DAYS, isNurtureName, isSpeedName, KNOWN_STANDARD_FORM_KEYS;
17894
18499
  var init_plan = __esm({
17895
18500
  "src/intake-to-build/plan.ts"() {
17896
18501
  "use strict";
17897
- import_zod57 = require("zod");
18502
+ import_zod58 = require("zod");
17898
18503
  REF_NAMESPACES = [
17899
18504
  "pipeline",
17900
18505
  "stage",
@@ -17915,29 +18520,29 @@ var init_plan = __esm({
17915
18520
  "sms_template"
17916
18521
  ];
17917
18522
  REF_RE = new RegExp(`^(${REF_NAMESPACES.join("|")})\\.[a-z0-9]+(_[a-z0-9]+)*$`);
17918
- refSchema = import_zod57.z.string().regex(REF_RE, "must be a <namespace>.<snake_case_slug> ref (no real GHL IDs)");
18523
+ refSchema = import_zod58.z.string().regex(REF_RE, "must be a <namespace>.<snake_case_slug> ref (no real GHL IDs)");
17919
18524
  USER_PENDING_REF = "user.__pending__";
17920
18525
  userRefSchema = nsRef("user");
17921
- userRefOrPendingSchema = import_zod57.z.union([userRefSchema, import_zod57.z.literal(USER_PENDING_REF)]);
18526
+ userRefOrPendingSchema = import_zod58.z.union([userRefSchema, import_zod58.z.literal(USER_PENDING_REF)]);
17922
18527
  USER_ROLES = ["admin", "user"];
17923
18528
  EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
17924
- userSchema = import_zod57.z.object({
18529
+ userSchema = import_zod58.z.object({
17925
18530
  ref: userRefSchema,
17926
- firstName: import_zod57.z.string().min(1),
17927
- lastName: import_zod57.z.string().min(1),
17928
- email: import_zod57.z.string().regex(EMAIL_RE, "must be an email address (GHL creates the login from it)"),
17929
- role: import_zod57.z.enum(USER_ROLES),
17930
- phone: import_zod57.z.string().optional()
18531
+ firstName: import_zod58.z.string().min(1),
18532
+ lastName: import_zod58.z.string().min(1),
18533
+ email: import_zod58.z.string().regex(EMAIL_RE, "must be an email address (GHL creates the login from it)"),
18534
+ role: import_zod58.z.enum(USER_ROLES),
18535
+ phone: import_zod58.z.string().optional()
17931
18536
  });
17932
- stageSchema = import_zod57.z.object({
18537
+ stageSchema = import_zod58.z.object({
17933
18538
  ref: nsRef("stage"),
17934
- name: import_zod57.z.string(),
17935
- position: import_zod57.z.number().int().nonnegative()
18539
+ name: import_zod58.z.string(),
18540
+ position: import_zod58.z.number().int().nonnegative()
17936
18541
  });
17937
- pipelineSchema = import_zod57.z.object({
18542
+ pipelineSchema = import_zod58.z.object({
17938
18543
  ref: nsRef("pipeline"),
17939
- name: import_zod57.z.string(),
17940
- stages: import_zod57.z.array(stageSchema).min(1)
18544
+ name: import_zod58.z.string(),
18545
+ stages: import_zod58.z.array(stageSchema).min(1)
17941
18546
  });
17942
18547
  GHL_FIELD_DATATYPES = [
17943
18548
  "TEXT",
@@ -17954,21 +18559,21 @@ var init_plan = __esm({
17954
18559
  "FILE_UPLOAD",
17955
18560
  "SIGNATURE"
17956
18561
  ];
17957
- customFieldSchema = import_zod57.z.object({
18562
+ customFieldSchema = import_zod58.z.object({
17958
18563
  ref: nsRef("field"),
17959
- name: import_zod57.z.string(),
17960
- dataType: import_zod57.z.enum(GHL_FIELD_DATATYPES),
17961
- model: import_zod57.z.enum(["contact", "opportunity"]).optional(),
17962
- options: import_zod57.z.array(import_zod57.z.string()).optional()
18564
+ name: import_zod58.z.string(),
18565
+ dataType: import_zod58.z.enum(GHL_FIELD_DATATYPES),
18566
+ model: import_zod58.z.enum(["contact", "opportunity"]).optional(),
18567
+ options: import_zod58.z.array(import_zod58.z.string()).optional()
17963
18568
  });
17964
- tagSchema = import_zod57.z.object({
18569
+ tagSchema = import_zod58.z.object({
17965
18570
  ref: nsRef("tag"),
17966
- name: import_zod57.z.string()
18571
+ name: import_zod58.z.string()
17967
18572
  });
17968
- customValueSchema = import_zod57.z.object({
18573
+ customValueSchema = import_zod58.z.object({
17969
18574
  ref: nsRef("cv"),
17970
- name: import_zod57.z.string(),
17971
- value: import_zod57.z.string().optional(),
18575
+ name: import_zod58.z.string(),
18576
+ value: import_zod58.z.string().optional(),
17972
18577
  filledBy: refSchema.optional()
17973
18578
  });
17974
18579
  CALENDAR_TYPES = [
@@ -17980,128 +18585,128 @@ var init_plan = __esm({
17980
18585
  ];
17981
18586
  TEAMLESS_CALENDAR_TYPES = /* @__PURE__ */ new Set(["event"]);
17982
18587
  SINGLE_STAFF_CALENDAR_TYPE = "round_robin";
17983
- openHoursBlockSchema = import_zod57.z.object({
17984
- daysOfTheWeek: import_zod57.z.array(import_zod57.z.number().int().min(0).max(6)),
17985
- hours: import_zod57.z.array(
17986
- import_zod57.z.object({
17987
- openHour: import_zod57.z.number().int().min(0).max(23),
17988
- openMinute: import_zod57.z.number().int().min(0).max(59),
17989
- closeHour: import_zod57.z.number().int().min(0).max(23),
17990
- closeMinute: import_zod57.z.number().int().min(0).max(59)
18588
+ openHoursBlockSchema = import_zod58.z.object({
18589
+ daysOfTheWeek: import_zod58.z.array(import_zod58.z.number().int().min(0).max(6)),
18590
+ hours: import_zod58.z.array(
18591
+ import_zod58.z.object({
18592
+ openHour: import_zod58.z.number().int().min(0).max(23),
18593
+ openMinute: import_zod58.z.number().int().min(0).max(59),
18594
+ closeHour: import_zod58.z.number().int().min(0).max(23),
18595
+ closeMinute: import_zod58.z.number().int().min(0).max(59)
17991
18596
  })
17992
18597
  )
17993
18598
  });
17994
18599
  CALENDAR_SLOT_UNITS = ["mins", "hours"];
17995
- calendarSchema = import_zod57.z.object({
18600
+ calendarSchema = import_zod58.z.object({
17996
18601
  ref: nsRef("calendar"),
17997
- name: import_zod57.z.string(),
17998
- calendarType: import_zod57.z.enum(CALENDAR_TYPES),
17999
- openHours: import_zod57.z.array(openHoursBlockSchema).optional(),
18000
- availabilityType: import_zod57.z.number().int().optional(),
18001
- requiresStaff: import_zod57.z.boolean().optional(),
18602
+ name: import_zod58.z.string(),
18603
+ calendarType: import_zod58.z.enum(CALENDAR_TYPES),
18604
+ openHours: import_zod58.z.array(openHoursBlockSchema).optional(),
18605
+ availabilityType: import_zod58.z.number().int().optional(),
18606
+ requiresStaff: import_zod58.z.boolean().optional(),
18002
18607
  /** Slot length (finding 25, 2026-08-26). GoHighLevel defaults to 30-minute
18003
18608
  * slots when this is omitted — a brief that says "Discovery Call, 15
18004
18609
  * minutes" must land here as slotDuration: 15. Unit defaults to "mins". */
18005
- slotDuration: import_zod57.z.number().int().positive().optional(),
18006
- slotDurationUnit: import_zod57.z.enum(CALENDAR_SLOT_UNITS).optional(),
18610
+ slotDuration: import_zod58.z.number().int().positive().optional(),
18611
+ slotDurationUnit: import_zod58.z.enum(CALENDAR_SLOT_UNITS).optional(),
18007
18612
  /** Minutes between slot start times (defaults to the slot duration in GHL). */
18008
- slotInterval: import_zod57.z.number().int().positive().optional(),
18613
+ slotInterval: import_zod58.z.number().int().positive().optional(),
18009
18614
  /** Minutes of buffer after each appointment. */
18010
- slotBuffer: import_zod57.z.number().int().min(0).optional(),
18615
+ slotBuffer: import_zod58.z.number().int().min(0).optional(),
18011
18616
  /** v2: the plan users on this calendar (the executor assigns them after the
18012
18617
  * users exist). Omitted → the executor's staff handoff applies as before. */
18013
- teamMemberRefs: import_zod57.z.array(userRefSchema).optional()
18618
+ teamMemberRefs: import_zod58.z.array(userRefSchema).optional()
18014
18619
  });
18015
- formFieldSchema = import_zod57.z.discriminatedUnion("type", [
18016
- import_zod57.z.object({
18017
- type: import_zod57.z.literal("standard"),
18018
- key: import_zod57.z.string(),
18019
- required: import_zod57.z.boolean().optional()
18620
+ formFieldSchema = import_zod58.z.discriminatedUnion("type", [
18621
+ import_zod58.z.object({
18622
+ type: import_zod58.z.literal("standard"),
18623
+ key: import_zod58.z.string(),
18624
+ required: import_zod58.z.boolean().optional()
18020
18625
  }),
18021
- import_zod57.z.object({
18022
- type: import_zod57.z.literal("custom"),
18626
+ import_zod58.z.object({
18627
+ type: import_zod58.z.literal("custom"),
18023
18628
  fieldRef: nsRef("field"),
18024
- required: import_zod57.z.boolean().optional()
18629
+ required: import_zod58.z.boolean().optional()
18025
18630
  })
18026
18631
  ]);
18027
- formSchema = import_zod57.z.object({
18632
+ formSchema = import_zod58.z.object({
18028
18633
  ref: nsRef("form"),
18029
- name: import_zod57.z.string(),
18030
- fields: import_zod57.z.array(formFieldSchema)
18634
+ name: import_zod58.z.string(),
18635
+ fields: import_zod58.z.array(formFieldSchema)
18031
18636
  });
18032
- pageSchema = import_zod57.z.object({
18637
+ pageSchema = import_zod58.z.object({
18033
18638
  ref: nsRef("page"),
18034
- name: import_zod57.z.string(),
18035
- role: import_zod57.z.string().optional(),
18036
- outline: import_zod57.z.string().optional(),
18639
+ name: import_zod58.z.string(),
18640
+ role: import_zod58.z.string().optional(),
18641
+ outline: import_zod58.z.string().optional(),
18037
18642
  formRef: nsRef("form").optional(),
18038
18643
  calendarRef: nsRef("calendar").optional()
18039
18644
  });
18040
18645
  FUNNEL_TARGETS = ["ghl", "external"];
18041
18646
  FUNNEL_HOSTS = ["cloudflare", "vercel"];
18042
- funnelSchema = import_zod57.z.object({
18647
+ funnelSchema = import_zod58.z.object({
18043
18648
  ref: nsRef("funnel"),
18044
- name: import_zod57.z.string(),
18649
+ name: import_zod58.z.string(),
18045
18650
  // Where the funnel is built. "ghl" (default) = funnel + named steps in GHL.
18046
18651
  // "external" = the subscriber builds + hosts the site themselves (Cloudflare/
18047
18652
  // Vercel) and wires its form back to this GHL sub-account (POWER-USER path —
18048
18653
  // see blueprint-funnel-targets-spec.md §9). The executor does NOT build or
18049
18654
  // deploy an external funnel; it surfaces the GHL-side wiring info.
18050
- target: import_zod57.z.enum(FUNNEL_TARGETS).optional(),
18051
- host: import_zod57.z.enum(FUNNEL_HOSTS).optional(),
18655
+ target: import_zod58.z.enum(FUNNEL_TARGETS).optional(),
18656
+ host: import_zod58.z.enum(FUNNEL_HOSTS).optional(),
18052
18657
  // external only
18053
- domain: import_zod57.z.string().optional(),
18658
+ domain: import_zod58.z.string().optional(),
18054
18659
  // external only
18055
- pages: import_zod57.z.array(pageSchema)
18660
+ pages: import_zod58.z.array(pageSchema)
18056
18661
  });
18057
- emailAssetSchema = import_zod57.z.object({
18662
+ emailAssetSchema = import_zod58.z.object({
18058
18663
  ref: nsRef("email"),
18059
- name: import_zod57.z.string(),
18060
- subject: import_zod57.z.string().optional(),
18061
- bodyOutline: import_zod57.z.string().optional(),
18062
- body: import_zod57.z.string().optional(),
18063
- mergeTags: import_zod57.z.array(import_zod57.z.string()).optional()
18664
+ name: import_zod58.z.string(),
18665
+ subject: import_zod58.z.string().optional(),
18666
+ bodyOutline: import_zod58.z.string().optional(),
18667
+ body: import_zod58.z.string().optional(),
18668
+ mergeTags: import_zod58.z.array(import_zod58.z.string()).optional()
18064
18669
  });
18065
- smsAssetSchema = import_zod57.z.object({
18670
+ smsAssetSchema = import_zod58.z.object({
18066
18671
  ref: nsRef("sms"),
18067
- name: import_zod57.z.string(),
18068
- bodyOutline: import_zod57.z.string().optional(),
18069
- body: import_zod57.z.string().optional(),
18070
- mergeTags: import_zod57.z.array(import_zod57.z.string()).optional()
18672
+ name: import_zod58.z.string(),
18673
+ bodyOutline: import_zod58.z.string().optional(),
18674
+ body: import_zod58.z.string().optional(),
18675
+ mergeTags: import_zod58.z.array(import_zod58.z.string()).optional()
18071
18676
  });
18072
- emailTemplateSchema = import_zod57.z.object({
18677
+ emailTemplateSchema = import_zod58.z.object({
18073
18678
  ref: nsRef("email_template"),
18074
- name: import_zod57.z.string().min(1),
18075
- subject: import_zod57.z.string().min(1),
18076
- html: import_zod57.z.string().min(1)
18679
+ name: import_zod58.z.string().min(1),
18680
+ subject: import_zod58.z.string().min(1),
18681
+ html: import_zod58.z.string().min(1)
18077
18682
  });
18078
- smsTemplateSchema = import_zod57.z.object({
18683
+ smsTemplateSchema = import_zod58.z.object({
18079
18684
  ref: nsRef("sms_template"),
18080
- name: import_zod57.z.string().min(1),
18081
- body: import_zod57.z.string().min(1)
18685
+ name: import_zod58.z.string().min(1),
18686
+ body: import_zod58.z.string().min(1)
18082
18687
  });
18083
- templatesSchema = import_zod57.z.object({
18084
- emails: import_zod57.z.array(emailTemplateSchema).optional(),
18085
- sms: import_zod57.z.array(smsTemplateSchema).optional()
18688
+ templatesSchema = import_zod58.z.object({
18689
+ emails: import_zod58.z.array(emailTemplateSchema).optional(),
18690
+ sms: import_zod58.z.array(smsTemplateSchema).optional()
18086
18691
  });
18087
- waitUnit = import_zod57.z.enum(["minutes", "hours", "days"]);
18692
+ waitUnit = import_zod58.z.enum(["minutes", "hours", "days"]);
18088
18693
  branchActionOptions = [
18089
- import_zod57.z.object({ type: import_zod57.z.literal("add_contact_tag"), tagRef: nsRef("tag") }),
18090
- import_zod57.z.object({ type: import_zod57.z.literal("remove_contact_tag"), tagRef: nsRef("tag") }),
18694
+ import_zod58.z.object({ type: import_zod58.z.literal("add_contact_tag"), tagRef: nsRef("tag") }),
18695
+ import_zod58.z.object({ type: import_zod58.z.literal("remove_contact_tag"), tagRef: nsRef("tag") }),
18091
18696
  // send_email / send_sms: point at a 5.8 asset (`emailRef`/`smsRef`, the 0.1
18092
18697
  // way) or a 5.8a template (`templateRef`, v2). At least one is required —
18093
18698
  // enforced by validateBuildPlan so the message reads the same as a dead ref.
18094
- import_zod57.z.object({
18095
- type: import_zod57.z.literal("send_email"),
18699
+ import_zod58.z.object({
18700
+ type: import_zod58.z.literal("send_email"),
18096
18701
  emailRef: nsRef("email").optional(),
18097
18702
  templateRef: nsRef("email_template").optional()
18098
18703
  }),
18099
- import_zod57.z.object({
18100
- type: import_zod57.z.literal("send_sms"),
18704
+ import_zod58.z.object({
18705
+ type: import_zod58.z.literal("send_sms"),
18101
18706
  smsRef: nsRef("sms").optional(),
18102
18707
  templateRef: nsRef("sms_template").optional()
18103
18708
  }),
18104
- import_zod57.z.object({ type: import_zod57.z.literal("wait"), value: import_zod57.z.number().positive(), unit: waitUnit }),
18709
+ import_zod58.z.object({ type: import_zod58.z.literal("wait"), value: import_zod58.z.number().positive(), unit: waitUnit }),
18105
18710
  // Appointment-relative wait ("wait until N BEFORE the appointment").
18106
18711
  // Only works when the workflow has an appointment in context (i.e. an
18107
18712
  // `appointment` trigger) — enforced by validateBuildPlan. Expands to GHL's
@@ -18110,91 +18715,91 @@ var init_plan = __esm({
18110
18715
  // (GHL stores whole minutes). Only "before" is emitted today — that's the
18111
18716
  // shape we captured + proved; "after" (post-appointment follow-up) is
18112
18717
  // deferred until its shape is captured from a real workflow.
18113
- import_zod57.z.object({
18114
- type: import_zod57.z.literal("wait_appointment"),
18115
- value: import_zod57.z.number().int().positive(),
18718
+ import_zod58.z.object({
18719
+ type: import_zod58.z.literal("wait_appointment"),
18720
+ value: import_zod58.z.number().int().positive(),
18116
18721
  unit: waitUnit
18117
18722
  }),
18118
18723
  // internal_notification: WHO gets pinged is a plan ref (`userRef`) resolved
18119
18724
  // by the executor, or the pending sentinel. `to` (a literal user id or the
18120
18725
  // old "assigned_user" hint) is still accepted for 0.1 plans and warned on.
18121
18726
  // One of the two is required — enforced by validateBuildPlan.
18122
- import_zod57.z.object({
18123
- type: import_zod57.z.literal("internal_notification"),
18124
- to: import_zod57.z.string().optional(),
18727
+ import_zod58.z.object({
18728
+ type: import_zod58.z.literal("internal_notification"),
18729
+ to: import_zod58.z.string().optional(),
18125
18730
  userRef: userRefOrPendingSchema.optional(),
18126
- title: import_zod57.z.string(),
18127
- body: import_zod57.z.string()
18731
+ title: import_zod58.z.string(),
18732
+ body: import_zod58.z.string()
18128
18733
  }),
18129
- import_zod57.z.object({
18130
- type: import_zod57.z.literal("update_contact_field"),
18734
+ import_zod58.z.object({
18735
+ type: import_zod58.z.literal("update_contact_field"),
18131
18736
  fieldRef: nsRef("field"),
18132
- value: import_zod57.z.string()
18737
+ value: import_zod58.z.string()
18133
18738
  }),
18134
- import_zod57.z.object({ type: import_zod57.z.literal("add_notes"), body: import_zod57.z.string() }),
18135
- import_zod57.z.object({
18136
- type: import_zod57.z.literal("task_notification"),
18137
- title: import_zod57.z.string(),
18138
- body: import_zod57.z.string().optional(),
18139
- dueDate: import_zod57.z.string().optional(),
18140
- assignedTo: import_zod57.z.string().optional(),
18739
+ import_zod58.z.object({ type: import_zod58.z.literal("add_notes"), body: import_zod58.z.string() }),
18740
+ import_zod58.z.object({
18741
+ type: import_zod58.z.literal("task_notification"),
18742
+ title: import_zod58.z.string(),
18743
+ body: import_zod58.z.string().optional(),
18744
+ dueDate: import_zod58.z.string().optional(),
18745
+ assignedTo: import_zod58.z.string().optional(),
18141
18746
  /** v2: the plan user the task is assigned to (or the pending sentinel). */
18142
18747
  userRef: userRefOrPendingSchema.optional()
18143
18748
  }),
18144
18749
  // assign_user (v2): GHL "Assign to user" — the contact's owner becomes the
18145
18750
  // referenced plan user. Round-robin among several users is a calendar
18146
18751
  // concern (teamMemberRefs), not this step's.
18147
- import_zod57.z.object({ type: import_zod57.z.literal("assign_user"), userRef: userRefOrPendingSchema }),
18148
- import_zod57.z.object({ type: import_zod57.z.literal("remove_from_workflow"), workflowRef: nsRef("workflow") }),
18149
- import_zod57.z.object({ type: import_zod57.z.literal("add_to_workflow"), workflowRef: nsRef("workflow") }),
18150
- import_zod57.z.object({
18151
- type: import_zod57.z.literal("create_opportunity"),
18752
+ import_zod58.z.object({ type: import_zod58.z.literal("assign_user"), userRef: userRefOrPendingSchema }),
18753
+ import_zod58.z.object({ type: import_zod58.z.literal("remove_from_workflow"), workflowRef: nsRef("workflow") }),
18754
+ import_zod58.z.object({ type: import_zod58.z.literal("add_to_workflow"), workflowRef: nsRef("workflow") }),
18755
+ import_zod58.z.object({
18756
+ type: import_zod58.z.literal("create_opportunity"),
18152
18757
  pipelineRef: nsRef("pipeline"),
18153
18758
  stageRef: nsRef("stage"),
18154
18759
  // Opportunity name (merge fields allowed). Defaults to the contact's name.
18155
18760
  // Required by GHL's create node; without it the create silently no-ops.
18156
- name: import_zod57.z.string().optional(),
18761
+ name: import_zod58.z.string().optional(),
18157
18762
  // Opportunity monetary value (the deal/sale dollar amount). A string so it
18158
18763
  // can be a literal ("2500") OR a merge field ("{{contact.package_value}}").
18159
18764
  // Optional — omitted → GHL leaves the value unset. Shape captured from Lux
18160
18765
  // Bio "14. Package Sale". Lux models the lifecycle by pipeline STAGE, not GHL
18161
18766
  // won/lost status, so a "won" opp = move to the closing stage WITH this value.
18162
- value: import_zod57.z.string().optional()
18767
+ value: import_zod58.z.string().optional()
18163
18768
  }),
18164
- import_zod57.z.object({
18165
- type: import_zod57.z.literal("update_opportunity"),
18769
+ import_zod58.z.object({
18770
+ type: import_zod58.z.literal("update_opportunity"),
18166
18771
  pipelineRef: nsRef("pipeline"),
18167
18772
  stageRef: nsRef("stage"),
18168
18773
  // Opportunity monetary value (see create_opportunity.value). Optional.
18169
- value: import_zod57.z.string().optional()
18774
+ value: import_zod58.z.string().optional()
18170
18775
  }),
18171
- import_zod57.z.object({
18172
- type: import_zod57.z.literal("goal_event"),
18173
- goalCondition: import_zod57.z.string(),
18776
+ import_zod58.z.object({
18777
+ type: import_zod58.z.literal("goal_event"),
18778
+ goalCondition: import_zod58.z.string(),
18174
18779
  // GHL's GoalAction enum (extracted 2026-05-18): continue | wait | exit.
18175
- action: import_zod57.z.enum(["exit", "continue", "wait"]).optional()
18780
+ action: import_zod58.z.enum(["exit", "continue", "wait"]).optional()
18176
18781
  })
18177
18782
  ];
18178
- branchActionSchema = import_zod57.z.discriminatedUnion("type", branchActionOptions);
18179
- findOpportunitySchema = import_zod57.z.object({
18180
- type: import_zod57.z.literal("find_opportunity"),
18783
+ branchActionSchema = import_zod58.z.discriminatedUnion("type", branchActionOptions);
18784
+ findOpportunitySchema = import_zod58.z.object({
18785
+ type: import_zod58.z.literal("find_opportunity"),
18181
18786
  pipelineRef: nsRef("pipeline"),
18182
- found: import_zod57.z.array(branchActionSchema).default([]),
18183
- notFound: import_zod57.z.array(branchActionSchema).default([])
18787
+ found: import_zod58.z.array(branchActionSchema).default([]),
18788
+ notFound: import_zod58.z.array(branchActionSchema).default([])
18184
18789
  });
18185
- actionSchema = import_zod57.z.discriminatedUnion("type", [...branchActionOptions, findOpportunitySchema]);
18790
+ actionSchema = import_zod58.z.discriminatedUnion("type", [...branchActionOptions, findOpportunitySchema]);
18186
18791
  APPOINTMENT_STATUSES = ["new", "confirmed", "showed", "noshow", "cancelled", "invalid"];
18187
18792
  CALL_STATUSES = ["busy", "canceled", "voicemail", "no-answer", "completed"];
18188
18793
  NUMBER_VALIDATION_STATES = ["not_valid", "sms_incapable"];
18189
- triggerSchema = import_zod57.z.object({
18190
- type: import_zod57.z.string(),
18794
+ triggerSchema = import_zod58.z.object({
18795
+ type: import_zod58.z.string(),
18191
18796
  formRef: nsRef("form").optional(),
18192
18797
  tagRef: nsRef("tag").optional(),
18193
18798
  calendarRef: nsRef("calendar").optional(),
18194
18799
  pipelineRef: nsRef("pipeline").optional(),
18195
18800
  stageRef: nsRef("stage").optional(),
18196
18801
  // Required for a native `appointment` trigger (the status it fires on).
18197
- appointmentStatus: import_zod57.z.enum(APPOINTMENT_STATUSES).optional(),
18802
+ appointmentStatus: import_zod58.z.enum(APPOINTMENT_STATUSES).optional(),
18198
18803
  // ── customer_reply scoping ──────────────────────────────────────
18199
18804
  // A bare customer_reply trigger fires on EVERY inbound reply from EVERY
18200
18805
  // contact. That is almost never what a plan means: "alert the owner when a
@@ -18208,27 +18813,27 @@ var init_plan = __esm({
18208
18813
  // Both optional and additive — omitting them keeps the long-standing
18209
18814
  // fires-on-any-reply baseline.
18210
18815
  hasTagRef: nsRef("tag").optional(),
18211
- replyIntent: import_zod57.z.enum(["positive", "negative"]).optional(),
18816
+ replyIntent: import_zod58.z.enum(["positive", "negative"]).optional(),
18212
18817
  // ── call_status scoping (missed-call text-back) ─────────────────
18213
18818
  // GHL's call_status trigger fires on a completed call attempt. The states it
18214
18819
  // exposes are exactly what a "missed call" means operationally. Required for
18215
18820
  // a call_status trigger — without them the trigger would fire on EVERY call
18216
18821
  // including answered ones, so the executor refuses rather than guess.
18217
- callStatuses: import_zod57.z.array(import_zod57.z.enum(CALL_STATUSES)).optional(),
18218
- callDirection: import_zod57.z.enum(["inbound", "outbound"]).optional(),
18822
+ callStatuses: import_zod58.z.array(import_zod58.z.enum(CALL_STATUSES)).optional(),
18823
+ callDirection: import_zod58.z.enum(["inbound", "outbound"]).optional(),
18219
18824
  // ── validation_error (GHL UI: "Number validation") ──────────────
18220
18825
  // Fires after a phone number passes or fails a validation check. The UI shows
18221
18826
  // "Not valid" / "SMS incapable"; the WIRE values are snake_case. Required for
18222
18827
  // a validation_error trigger — without them the executor refuses rather than
18223
18828
  // guess which failure states to fire on.
18224
- numberValidation: import_zod57.z.array(import_zod57.z.enum(NUMBER_VALIDATION_STATES)).optional()
18829
+ numberValidation: import_zod58.z.array(import_zod58.z.enum(NUMBER_VALIDATION_STATES)).optional()
18225
18830
  });
18226
- workflowSchema = import_zod57.z.object({
18831
+ workflowSchema = import_zod58.z.object({
18227
18832
  ref: nsRef("workflow"),
18228
- name: import_zod57.z.string(),
18833
+ name: import_zod58.z.string(),
18229
18834
  trigger: triggerSchema.optional(),
18230
- stopOnResponse: import_zod57.z.boolean().optional(),
18231
- actions: import_zod57.z.array(actionSchema).max(40)
18835
+ stopOnResponse: import_zod58.z.boolean().optional(),
18836
+ actions: import_zod58.z.array(actionSchema).max(40)
18232
18837
  // house rule: <=40 actions/workflow
18233
18838
  });
18234
18839
  HANDOFF_OWNER_LEGACY = {
@@ -18236,37 +18841,37 @@ var init_plan = __esm({
18236
18841
  "JERRY-EXT": "OPERATOR-EXT",
18237
18842
  "SASHA": "TEAM"
18238
18843
  };
18239
- handoffSchema = import_zod57.z.object({
18844
+ handoffSchema = import_zod58.z.object({
18240
18845
  ref: nsRef("handoff"),
18241
- owner: import_zod57.z.enum(["OPERATOR-UI", "OPERATOR-EXT", "TEAM", "JERRY-UI", "JERRY-EXT", "SASHA"]).transform((o) => HANDOFF_OWNER_LEGACY[o] ?? o),
18242
- title: import_zod57.z.string(),
18243
- trigger: import_zod57.z.string().optional(),
18244
- instruction: import_zod57.z.string(),
18846
+ owner: import_zod58.z.enum(["OPERATOR-UI", "OPERATOR-EXT", "TEAM", "JERRY-UI", "JERRY-EXT", "SASHA"]).transform((o) => HANDOFF_OWNER_LEGACY[o] ?? o),
18847
+ title: import_zod58.z.string(),
18848
+ trigger: import_zod58.z.string().optional(),
18849
+ instruction: import_zod58.z.string(),
18245
18850
  produces: refSchema.nullable().optional(),
18246
- successCheck: import_zod57.z.string(),
18247
- blocks: import_zod57.z.array(import_zod57.z.string()).optional()
18851
+ successCheck: import_zod58.z.string(),
18852
+ blocks: import_zod58.z.array(import_zod58.z.string()).optional()
18248
18853
  });
18249
- buildPlanSchema = import_zod57.z.object({
18250
- schemaVersion: import_zod57.z.string(),
18251
- planId: import_zod57.z.string(),
18252
- briefId: import_zod57.z.string(),
18253
- preset: import_zod57.z.string(),
18254
- summary: import_zod57.z.string().optional(),
18255
- users: import_zod57.z.array(userSchema).optional(),
18256
- pipelines: import_zod57.z.array(pipelineSchema).optional(),
18257
- customFields: import_zod57.z.array(customFieldSchema).optional(),
18258
- tags: import_zod57.z.array(tagSchema).optional(),
18259
- customValues: import_zod57.z.array(customValueSchema).optional(),
18260
- calendars: import_zod57.z.array(calendarSchema).optional(),
18261
- forms: import_zod57.z.array(formSchema).optional(),
18262
- funnels: import_zod57.z.array(funnelSchema).optional(),
18263
- emails: import_zod57.z.array(emailAssetSchema).optional(),
18264
- sms: import_zod57.z.array(smsAssetSchema).optional(),
18854
+ buildPlanSchema = import_zod58.z.object({
18855
+ schemaVersion: import_zod58.z.string(),
18856
+ planId: import_zod58.z.string(),
18857
+ briefId: import_zod58.z.string(),
18858
+ preset: import_zod58.z.string(),
18859
+ summary: import_zod58.z.string().optional(),
18860
+ users: import_zod58.z.array(userSchema).optional(),
18861
+ pipelines: import_zod58.z.array(pipelineSchema).optional(),
18862
+ customFields: import_zod58.z.array(customFieldSchema).optional(),
18863
+ tags: import_zod58.z.array(tagSchema).optional(),
18864
+ customValues: import_zod58.z.array(customValueSchema).optional(),
18865
+ calendars: import_zod58.z.array(calendarSchema).optional(),
18866
+ forms: import_zod58.z.array(formSchema).optional(),
18867
+ funnels: import_zod58.z.array(funnelSchema).optional(),
18868
+ emails: import_zod58.z.array(emailAssetSchema).optional(),
18869
+ sms: import_zod58.z.array(smsAssetSchema).optional(),
18265
18870
  templates: templatesSchema.optional(),
18266
- workflows: import_zod57.z.array(workflowSchema).optional(),
18267
- handoffs: import_zod57.z.array(handoffSchema).optional(),
18268
- buildOrder: import_zod57.z.array(import_zod57.z.string()).optional(),
18269
- idMap: import_zod57.z.record(import_zod57.z.string()).optional()
18871
+ workflows: import_zod58.z.array(workflowSchema).optional(),
18872
+ handoffs: import_zod58.z.array(handoffSchema).optional(),
18873
+ buildOrder: import_zod58.z.array(import_zod58.z.string()).optional(),
18874
+ idMap: import_zod58.z.record(import_zod58.z.string()).optional()
18270
18875
  }).strict();
18271
18876
  PLAN_ERROR_CODES = {
18272
18877
  /** A workflow named like /nurture/i whose waits add up to < 30 days. */
@@ -19681,10 +20286,10 @@ function progressLine(e) {
19681
20286
  }
19682
20287
  }
19683
20288
  function defaultProgressDir(base = appDataDir()) {
19684
- return path11.join(base, "progress");
20289
+ return path12.join(base, "progress");
19685
20290
  }
19686
20291
  function progressFilePath(dir, locationId2) {
19687
- return path11.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
20292
+ return path12.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
19688
20293
  }
19689
20294
  function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DIR_ENV]) {
19690
20295
  if (!dir) return null;
@@ -19692,10 +20297,10 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
19692
20297
  const file = progressFilePath(dir, locationId2);
19693
20298
  const write = () => {
19694
20299
  try {
19695
- fs12.mkdirSync(dir, { recursive: true, mode: 448 });
20300
+ fs13.mkdirSync(dir, { recursive: true, mode: 448 });
19696
20301
  const tmp = `${file}.tmp`;
19697
- fs12.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
19698
- fs12.renameSync(tmp, file);
20302
+ fs13.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
20303
+ fs13.renameSync(tmp, file);
19699
20304
  } catch {
19700
20305
  }
19701
20306
  };
@@ -19713,7 +20318,7 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
19713
20318
  }
19714
20319
  function readProgressSnapshot(dir, locationId2) {
19715
20320
  try {
19716
- const raw = JSON.parse(fs12.readFileSync(progressFilePath(dir, locationId2), "utf8"));
20321
+ const raw = JSON.parse(fs13.readFileSync(progressFilePath(dir, locationId2), "utf8"));
19717
20322
  const p = raw?.progress;
19718
20323
  if (!p || !Array.isArray(p.groups) || typeof p.updatedAt !== "string") return null;
19719
20324
  return p;
@@ -19734,12 +20339,12 @@ function tapPush(arr, onItem) {
19734
20339
  };
19735
20340
  Object.defineProperty(arr, "push", { value: tapped, writable: true, configurable: true, enumerable: false });
19736
20341
  }
19737
- var fs12, path11, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
20342
+ var fs13, path12, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
19738
20343
  var init_build_progress = __esm({
19739
20344
  "src/build-progress.ts"() {
19740
20345
  "use strict";
19741
- fs12 = __toESM(require("fs"));
19742
- path11 = __toESM(require("path"));
20346
+ fs13 = __toESM(require("fs"));
20347
+ path12 = __toESM(require("path"));
19743
20348
  init_executor();
19744
20349
  init_credentials_store();
19745
20350
  PROGRESS_GROUPS = [
@@ -19882,14 +20487,14 @@ var init_build_progress = __esm({
19882
20487
 
19883
20488
  // src/build-journal.ts
19884
20489
  function journalDir(base = appDataDir()) {
19885
- return path12.join(base, "build-journal");
20490
+ return path13.join(base, "build-journal");
19886
20491
  }
19887
20492
  function journalPath(locationId2, base) {
19888
- return path12.join(journalDir(base), `${locationId2}.jsonl`);
20493
+ return path13.join(journalDir(base), `${locationId2}.jsonl`);
19889
20494
  }
19890
20495
  function newRunId(now = /* @__PURE__ */ new Date(), rand = defaultRand) {
19891
- const iso = now.toISOString();
19892
- const stamp = `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}T${iso.slice(11, 13)}${iso.slice(14, 16)}`;
20496
+ const iso2 = now.toISOString();
20497
+ const stamp = `${iso2.slice(0, 4)}${iso2.slice(5, 7)}${iso2.slice(8, 10)}T${iso2.slice(11, 13)}${iso2.slice(14, 16)}`;
19893
20498
  return `r-${stamp}-${rand()}`;
19894
20499
  }
19895
20500
  function defaultRand() {
@@ -19931,16 +20536,16 @@ function buildRunRecord(input) {
19931
20536
  }
19932
20537
  function appendJournal(record, base) {
19933
20538
  const dir = journalDir(base);
19934
- fs13.mkdirSync(dir, { recursive: true, mode: 448 });
20539
+ fs14.mkdirSync(dir, { recursive: true, mode: 448 });
19935
20540
  const file = journalPath(record.locationId, base);
19936
- fs13.appendFileSync(file, `${JSON.stringify(record)}
20541
+ fs14.appendFileSync(file, `${JSON.stringify(record)}
19937
20542
  `, { mode: 384 });
19938
20543
  return file;
19939
20544
  }
19940
20545
  function readJournal(locationId2, base) {
19941
20546
  let raw;
19942
20547
  try {
19943
- raw = fs13.readFileSync(journalPath(locationId2, base), "utf8");
20548
+ raw = fs14.readFileSync(journalPath(locationId2, base), "utf8");
19944
20549
  } catch {
19945
20550
  return [];
19946
20551
  }
@@ -19984,12 +20589,12 @@ function describeRuns(locationId2, base) {
19984
20589
  reverted: reverted.has(r.runId)
19985
20590
  })).reverse();
19986
20591
  }
19987
- var fs13, path12;
20592
+ var fs14, path13;
19988
20593
  var init_build_journal = __esm({
19989
20594
  "src/build-journal.ts"() {
19990
20595
  "use strict";
19991
- fs13 = __toESM(require("fs"));
19992
- path12 = __toESM(require("path"));
20596
+ fs14 = __toESM(require("fs"));
20597
+ path13 = __toESM(require("path"));
19993
20598
  init_credentials_store();
19994
20599
  }
19995
20600
  });
@@ -19997,11 +20602,11 @@ var init_build_journal = __esm({
19997
20602
  // src/stage-runner.ts
19998
20603
  function accountRulesPath() {
19999
20604
  const override = process.env[ACCOUNT_RULES_ENV];
20000
- return override && override.trim() ? override : path13.join(appDataDir(), "account-rules.json");
20605
+ return override && override.trim() ? override : path14.join(appDataDir(), "account-rules.json");
20001
20606
  }
20002
20607
  function readAccountRules(file = accountRulesPath()) {
20003
20608
  try {
20004
- const raw = JSON.parse(fs14.readFileSync(file, "utf8"));
20609
+ const raw = JSON.parse(fs15.readFileSync(file, "utf8"));
20005
20610
  const pick = (v) => {
20006
20611
  if (!v || typeof v !== "object" || Array.isArray(v)) return {};
20007
20612
  return Object.fromEntries(
@@ -20053,10 +20658,10 @@ function stageBlockedBy(stages, stage) {
20053
20658
  return null;
20054
20659
  }
20055
20660
  function claudeBin() {
20056
- const fallback = path13.join(os3.homedir(), ".local", "bin", "claude");
20057
- const fromPath = (process.env.PATH || "").split(path13.delimiter).map((d) => path13.join(d, "claude")).find((p) => {
20661
+ const fallback = path14.join(os3.homedir(), ".local", "bin", "claude");
20662
+ const fromPath = (process.env.PATH || "").split(path14.delimiter).map((d) => path14.join(d, "claude")).find((p) => {
20058
20663
  try {
20059
- fs14.accessSync(p, fs14.constants.X_OK);
20664
+ fs15.accessSync(p, fs15.constants.X_OK);
20060
20665
  return true;
20061
20666
  } catch {
20062
20667
  return false;
@@ -20064,7 +20669,7 @@ function claudeBin() {
20064
20669
  });
20065
20670
  if (fromPath) return fromPath;
20066
20671
  try {
20067
- fs14.accessSync(fallback, fs14.constants.X_OK);
20672
+ fs15.accessSync(fallback, fs15.constants.X_OK);
20068
20673
  return fallback;
20069
20674
  } catch {
20070
20675
  }
@@ -20121,7 +20726,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
20121
20726
  const progressDir = opts.onProgress ? defaultProgressDir() : void 0;
20122
20727
  if (progressDir) {
20123
20728
  try {
20124
- fs14.rmSync(progressFilePath(progressDir, locationId2), { force: true });
20729
+ fs15.rmSync(progressFilePath(progressDir, locationId2), { force: true });
20125
20730
  } catch {
20126
20731
  }
20127
20732
  }
@@ -20205,14 +20810,14 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
20205
20810
  });
20206
20811
  });
20207
20812
  }
20208
- var import_child_process3, fs14, os3, path13, EMPTY_RULES, ACCOUNT_RULES_ENV, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
20813
+ var import_child_process3, fs15, os3, path14, EMPTY_RULES, ACCOUNT_RULES_ENV, SANDBOX_ALLOWLIST, PROTECTED_LOCATIONS, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
20209
20814
  var init_stage_runner = __esm({
20210
20815
  "src/stage-runner.ts"() {
20211
20816
  "use strict";
20212
20817
  import_child_process3 = require("child_process");
20213
- fs14 = __toESM(require("fs"));
20818
+ fs15 = __toESM(require("fs"));
20214
20819
  os3 = __toESM(require("os"));
20215
- path13 = __toESM(require("path"));
20820
+ path14 = __toESM(require("path"));
20216
20821
  init_credentials_store();
20217
20822
  init_stage_boundary();
20218
20823
  init_plan_guide();
@@ -20407,23 +21012,23 @@ function friendlyDate(stamp) {
20407
21012
  if (isNaN(d.getTime())) return stamp;
20408
21013
  return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" });
20409
21014
  }
20410
- var import_zod58, RunnerCheckSchema, RunnerResultSchema;
21015
+ var import_zod59, RunnerCheckSchema, RunnerResultSchema;
20411
21016
  var init_handoff_pack = __esm({
20412
21017
  "src/command-os/handoff-pack.ts"() {
20413
21018
  "use strict";
20414
- import_zod58 = require("zod");
20415
- RunnerCheckSchema = import_zod58.z.object({
20416
- name: import_zod58.z.string().min(1),
20417
- state: import_zod58.z.enum(["CHECKED", "DONE_UNCHECKED"]),
20418
- evidence: import_zod58.z.string().optional(),
20419
- reason: import_zod58.z.string().optional(),
21019
+ import_zod59 = require("zod");
21020
+ RunnerCheckSchema = import_zod59.z.object({
21021
+ name: import_zod59.z.string().min(1),
21022
+ state: import_zod59.z.enum(["CHECKED", "DONE_UNCHECKED"]),
21023
+ evidence: import_zod59.z.string().optional(),
21024
+ reason: import_zod59.z.string().optional(),
20420
21025
  /** One sentence a client can read: what is in place, or what is missing and why. No tool names, no ids. */
20421
- plain: import_zod58.z.string().optional()
21026
+ plain: import_zod59.z.string().optional()
20422
21027
  }).strict();
20423
- RunnerResultSchema = import_zod58.z.object({
20424
- ok: import_zod58.z.boolean(),
20425
- checks: import_zod58.z.array(RunnerCheckSchema).default([]),
20426
- error: import_zod58.z.string().optional()
21028
+ RunnerResultSchema = import_zod59.z.object({
21029
+ ok: import_zod59.z.boolean(),
21030
+ checks: import_zod59.z.array(RunnerCheckSchema).default([]),
21031
+ error: import_zod59.z.string().optional()
20427
21032
  }).strict();
20428
21033
  }
20429
21034
  });
@@ -20837,7 +21442,7 @@ var init_work_queue = __esm({
20837
21442
  // src/board-roster.ts
20838
21443
  function readBoardState() {
20839
21444
  try {
20840
- const raw = JSON.parse(fs15.readFileSync(path14.join(appDataDir(), "cockpit-state.json"), "utf8"));
21445
+ const raw = JSON.parse(fs16.readFileSync(path15.join(appDataDir(), "cockpit-state.json"), "utf8"));
20841
21446
  if (raw && typeof raw === "object" && raw.clients && typeof raw.clients === "object") return raw;
20842
21447
  } catch {
20843
21448
  }
@@ -20854,12 +21459,12 @@ function knownClients(registry2, state) {
20854
21459
  }
20855
21460
  return [...out.values()];
20856
21461
  }
20857
- var fs15, path14;
21462
+ var fs16, path15;
20858
21463
  var init_board_roster = __esm({
20859
21464
  "src/board-roster.ts"() {
20860
21465
  "use strict";
20861
- fs15 = __toESM(require("fs"));
20862
- path14 = __toESM(require("path"));
21466
+ fs16 = __toESM(require("fs"));
21467
+ path15 = __toESM(require("path"));
20863
21468
  init_credentials_store();
20864
21469
  init_client_engagements();
20865
21470
  }
@@ -20894,7 +21499,7 @@ function registerClientEngagementTools(server2, registry2) {
20894
21499
  server2,
20895
21500
  "get_client_engagements",
20896
21501
  "Read what each of YOUR clients bought \u2014 the offer they are on, what they pay, monthly or one-time, when it started, when it renews, who runs the account, and any SOPs specific to them. Use this before quoting, renewing, writing a proposal or planning a client's week, instead of asking the user again. Also returns your recurring revenue as a FLOOR (the sum of active monthly clients that have a price recorded) plus a count of the ones that do not, because a total assembled from partial data is not a total. Pass locationId for one client, or nothing for all of them. The locationId in the reply is for calling set_client_engagement \u2014 refer to clients by name when you answer the user, never by id. Local file on this machine; never touches GoHighLevel.",
20897
- { locationId: import_zod59.z.string().optional().describe("One client's sub-account id. Omit for the whole roster.") },
21502
+ { locationId: import_zod60.z.string().optional().describe("One client's sub-account id. Omit for the whole roster.") },
20898
21503
  async ({ locationId: locationId2 }) => {
20899
21504
  const profile = readAgencyProfile();
20900
21505
  const saved = readClientEngagements();
@@ -20924,24 +21529,24 @@ function registerClientEngagementTools(server2, registry2) {
20924
21529
  "set_client_engagement",
20925
21530
  "Record or update what ONE client bought \u2014 offer, price, monthly or one-time, start and renewal dates, status, who runs the account, and SOPs specific to them. Send only the fields you are changing; anything you omit is left exactly as it was, so this is safe to call repeatedly. To CLEAR a field pass null for it, and to remove the client's record entirely pass remove:true. Dates are YYYY-MM-DD. Local file on this machine; never touches GoHighLevel.",
20926
21531
  {
20927
- locationId: import_zod59.z.string().describe("The client's GHL sub-account id \u2014 the same id the board uses."),
21532
+ locationId: import_zod60.z.string().describe("The client's GHL sub-account id \u2014 the same id the board uses."),
20928
21533
  // Every editable field is NULLABLE, not just optional. The description
20929
21534
  // promises "pass null to clear", and a schema that rejects null would make
20930
21535
  // that promise a lie the tests never caught, because they cleared fields by
20931
21536
  // calling the store directly instead of through this surface (Codex,
20932
21537
  // 2026-08-28). Absent = untouched; null = clear; a value = set.
20933
- clientName: import_zod59.z.string().nullable().optional().describe("How you refer to them, if it differs from the account name."),
20934
- offer: import_zod59.z.string().nullable().optional().describe("Which of your offers they are on. Matched against your agency profile so a typo is reported."),
20935
- price: import_zod59.z.number().min(0).nullable().optional().describe("What they actually pay \u2014 not your floor."),
20936
- cadence: import_zod59.z.enum(["one-time", "monthly"]).nullable().optional(),
20937
- currency: import_zod59.z.string().nullable().optional(),
20938
- startedOn: import_zod59.z.string().nullable().optional().describe("YYYY-MM-DD."),
20939
- renewsOn: import_zod59.z.string().nullable().optional().describe("YYYY-MM-DD."),
20940
- status: import_zod59.z.enum(["active", "paused", "ended"]).nullable().optional(),
20941
- owner: import_zod59.z.string().nullable().optional().describe("Which staff member runs this account."),
20942
- sops: import_zod59.z.array(import_zod59.z.string()).nullable().optional().describe("SOPs that apply to this client only. Leave empty to use your agency's."),
20943
- notes: import_zod59.z.string().nullable().optional(),
20944
- remove: import_zod59.z.boolean().optional().describe("Delete this client's record entirely.")
21538
+ clientName: import_zod60.z.string().nullable().optional().describe("How you refer to them, if it differs from the account name."),
21539
+ offer: import_zod60.z.string().nullable().optional().describe("Which of your offers they are on. Matched against your agency profile so a typo is reported."),
21540
+ price: import_zod60.z.number().min(0).nullable().optional().describe("What they actually pay \u2014 not your floor."),
21541
+ cadence: import_zod60.z.enum(["one-time", "monthly"]).nullable().optional(),
21542
+ currency: import_zod60.z.string().nullable().optional(),
21543
+ startedOn: import_zod60.z.string().nullable().optional().describe("YYYY-MM-DD."),
21544
+ renewsOn: import_zod60.z.string().nullable().optional().describe("YYYY-MM-DD."),
21545
+ status: import_zod60.z.enum(["active", "paused", "ended"]).nullable().optional(),
21546
+ owner: import_zod60.z.string().nullable().optional().describe("Which staff member runs this account."),
21547
+ sops: import_zod60.z.array(import_zod60.z.string()).nullable().optional().describe("SOPs that apply to this client only. Leave empty to use your agency's."),
21548
+ notes: import_zod60.z.string().nullable().optional(),
21549
+ remove: import_zod60.z.boolean().optional().describe("Delete this client's record entirely.")
20945
21550
  },
20946
21551
  async (args) => {
20947
21552
  const { locationId: locationId2, remove, ...rest } = args;
@@ -20965,7 +21570,7 @@ function registerClientEngagementTools(server2, registry2) {
20965
21570
  server2,
20966
21571
  "client_this_week",
20967
21572
  "Answer 'what should I be doing for this client this week' for ONE client, from what you already know: what they bought, where their build has stopped, what is waiting on you versus on Command OS, a renewal that is close, whether the price is under your own floor, and the SOP that applies. Names the client by name or by account id. Everything it cannot know is listed plainly rather than guessed. Local files on this machine; never touches GoHighLevel.",
20968
- { client: import_zod59.z.string().describe("The client's name as you refer to them, or their sub-account id.") },
21573
+ { client: import_zod60.z.string().describe("The client's name as you refer to them, or their sub-account id.") },
20969
21574
  async ({ client }) => {
20970
21575
  const state = readBoardState();
20971
21576
  const roster = knownClients(registry2, state);
@@ -20985,11 +21590,11 @@ function registerClientEngagementTools(server2, registry2) {
20985
21590
  }
20986
21591
  );
20987
21592
  }
20988
- var import_zod59;
21593
+ var import_zod60;
20989
21594
  var init_client_engagements2 = __esm({
20990
21595
  "src/tools/client-engagements.ts"() {
20991
21596
  "use strict";
20992
- import_zod59 = require("zod");
21597
+ import_zod60 = require("zod");
20993
21598
  init_tool_helpers();
20994
21599
  init_agency_profile();
20995
21600
  init_client_engagements();
@@ -21449,14 +22054,14 @@ function registerAuditReportTools(server2) {
21449
22054
  "build_audit_report",
21450
22055
  "Turn the output of `audit_workflows` into a client-ready assessment: plain-English findings with money attached where it can honestly be attached, ranked worst-first, plus an explicit list of what could NOT be checked. Call `audit_workflows` first and pass its result here \u2014 this does not re-read the account. Give `leadsPerMonth`, `closeRate` and `monthlyValuePerClient` if you know them and the findings get costed in dollars; leave them out and it says which input is missing instead of guessing. `monthlyValuePerClient` defaults to your saved minimum monthly fee from your agency profile. Use this to produce the document you send or present, not to diagnose \u2014 the diagnosing already happened.",
21451
22056
  {
21452
- audit: import_zod60.z.record(import_zod60.z.unknown()).describe("The full JSON result returned by audit_workflows."),
21453
- audience: import_zod60.z.enum(["internal", "prospect"]).optional().describe("Who reads it. 'internal' (default) is a report about an account you already run, and may use your own minimum monthly as what a client is worth. 'prospect' is a document about somebody else's business: your numbers are NEVER used, so findings stay uncosted unless you supply THEIR lead volume, close rate and client value."),
21454
- accountName: import_zod60.z.string().describe("The client's name as they should see it on the document."),
21455
- locationId: import_zod60.z.string().optional().describe("Sub-account id. Recorded on the document, never shown in the body."),
21456
- leadsPerMonth: import_zod60.z.number().min(0).optional().describe("Leads this account actually receives per month, if known. Without it, findings are not costed in dollars."),
21457
- closeRate: import_zod60.z.number().min(0).max(1).optional().describe("Share of reached leads that become clients, 0-1. Never guessed."),
21458
- monthlyValuePerClient: import_zod60.z.number().min(0).optional().describe("What one new client is worth per month. Defaults to your agency profile's minimum monthly fee."),
21459
- generatedAt: import_zod60.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
22057
+ audit: import_zod61.z.record(import_zod61.z.unknown()).describe("The full JSON result returned by audit_workflows."),
22058
+ audience: import_zod61.z.enum(["internal", "prospect"]).optional().describe("Who reads it. 'internal' (default) is a report about an account you already run, and may use your own minimum monthly as what a client is worth. 'prospect' is a document about somebody else's business: your numbers are NEVER used, so findings stay uncosted unless you supply THEIR lead volume, close rate and client value."),
22059
+ accountName: import_zod61.z.string().describe("The client's name as they should see it on the document."),
22060
+ locationId: import_zod61.z.string().optional().describe("Sub-account id. Recorded on the document, never shown in the body."),
22061
+ leadsPerMonth: import_zod61.z.number().min(0).optional().describe("Leads this account actually receives per month, if known. Without it, findings are not costed in dollars."),
22062
+ closeRate: import_zod61.z.number().min(0).max(1).optional().describe("Share of reached leads that become clients, 0-1. Never guessed."),
22063
+ monthlyValuePerClient: import_zod61.z.number().min(0).optional().describe("What one new client is worth per month. Defaults to your agency profile's minimum monthly fee."),
22064
+ generatedAt: import_zod61.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
21460
22065
  },
21461
22066
  async (args) => {
21462
22067
  const a = args;
@@ -21514,12 +22119,12 @@ function registerAuditReportTools(server2) {
21514
22119
  "build_site_audit_report",
21515
22120
  "Turn a website read-through into the assessment document you put in front of a PROSPECT \u2014 someone whose GoHighLevel account you do not have. Pass the verified observations from a site crawl and it returns plain-English findings, ranked, each one carrying the quote and the page it came from. Use it before a sales call, or alongside a receptionist demo, so the same read of their site fills both. It never invents money: a website cannot tell you what a lead is worth to that business, so findings come back uncosted unless you supply THEIR figures, and the document says why. It also never repeats a claim the crawl could not stand up \u2014 anything refused is returned separately, so you can see what was thrown out rather than wonder what was missed.",
21516
22121
  {
21517
- observations: import_zod60.z.record(import_zod60.z.unknown()).describe("The verified observations payload from the site read: site, pagesRead, observations, limits. Passed through as-is."),
21518
- businessName: import_zod60.z.string().describe("The business's name as it should appear on the document."),
21519
- leadsPerMonth: import_zod60.z.number().min(0).optional().describe("THEIR leads per month, if they told you. Never your own figure."),
21520
- closeRate: import_zod60.z.number().min(0).max(1).optional().describe("THEIR close rate, 0-1, if they told you. Never guessed."),
21521
- monthlyValuePerClient: import_zod60.z.number().min(0).optional().describe("What one client is worth to THEM per month, if they told you."),
21522
- generatedAt: import_zod60.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
22122
+ observations: import_zod61.z.record(import_zod61.z.unknown()).describe("The verified observations payload from the site read: site, pagesRead, observations, limits. Passed through as-is."),
22123
+ businessName: import_zod61.z.string().describe("The business's name as it should appear on the document."),
22124
+ leadsPerMonth: import_zod61.z.number().min(0).optional().describe("THEIR leads per month, if they told you. Never your own figure."),
22125
+ closeRate: import_zod61.z.number().min(0).max(1).optional().describe("THEIR close rate, 0-1, if they told you. Never guessed."),
22126
+ monthlyValuePerClient: import_zod61.z.number().min(0).optional().describe("What one client is worth to THEM per month, if they told you."),
22127
+ generatedAt: import_zod61.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
21523
22128
  },
21524
22129
  async (args) => {
21525
22130
  const a = args;
@@ -21578,11 +22183,11 @@ function registerAuditReportTools(server2) {
21578
22183
  }
21579
22184
  );
21580
22185
  }
21581
- var import_zod60;
22186
+ var import_zod61;
21582
22187
  var init_audit_report2 = __esm({
21583
22188
  "src/tools/audit-report.ts"() {
21584
22189
  "use strict";
21585
- import_zod60 = require("zod");
22190
+ import_zod61 = require("zod");
21586
22191
  init_tool_helpers();
21587
22192
  init_audit_report();
21588
22193
  init_audit_translate();
@@ -21950,8 +22555,8 @@ function registerDailyBriefTools(server2, registry2) {
21950
22555
  "daily_brief",
21951
22556
  "The morning brief across ALL your clients: today's appointments first, then what is waiting on YOU, what Command OS is running, renewals inside 30 days, clients under your own price floor, and new leads overnight. Use it to answer 'what do I need to do today' or 'anything I'm missing' without opening a single sub-account. Reads today's calendar and new-contact counts live from each connected account; pass live=false for records only. Accounts that could not be read are named with the reason \u2014 it never reports an account as quiet when it simply could not get in.",
21952
22557
  {
21953
- live: import_zod61.z.boolean().optional().describe("Read today's calendars and lead counts from the connected accounts. Default true. false answers from local records alone."),
21954
- hoursBack: import_zod61.z.number().min(1).max(72).optional().describe("Count new leads over this many hours instead of since midnight.")
22558
+ live: import_zod62.z.boolean().optional().describe("Read today's calendars and lead counts from the connected accounts. Default true. false answers from local records alone."),
22559
+ hoursBack: import_zod62.z.number().min(1).max(72).optional().describe("Count new leads over this many hours instead of since midnight.")
21955
22560
  },
21956
22561
  async ({ live, hoursBack }) => {
21957
22562
  const now = /* @__PURE__ */ new Date();
@@ -21985,11 +22590,11 @@ function registerDailyBriefTools(server2, registry2) {
21985
22590
  }
21986
22591
  );
21987
22592
  }
21988
- var import_zod61;
22593
+ var import_zod62;
21989
22594
  var init_daily_brief2 = __esm({
21990
22595
  "src/tools/daily-brief.ts"() {
21991
22596
  "use strict";
21992
- import_zod61 = require("zod");
22597
+ import_zod62 = require("zod");
21993
22598
  init_tool_helpers();
21994
22599
  init_ghl_client();
21995
22600
  init_agency_profile();
@@ -22042,7 +22647,7 @@ function registerSnapshotTools(server2, client, registry2) {
22042
22647
  "list_snapshots",
22043
22648
  "List the agency's GHL snapshots (id, name, type) so you can pick the right one by name before applying it to a sub-account. Reads the agency/company-scoped key (not a sub-account PIT). companyId defaults to the active location's company; pass it explicitly to target a different company you have the agency key for. Read-only.",
22044
22649
  {
22045
- companyId: import_zod62.z.string().optional().describe(
22650
+ companyId: import_zod63.z.string().optional().describe(
22046
22651
  "Agency/company ID whose snapshots to list. Defaults to the active location's company. Must match the company your agency key is scoped to."
22047
22652
  )
22048
22653
  },
@@ -22067,11 +22672,11 @@ function registerSnapshotTools(server2, client, registry2) {
22067
22672
  "create_snapshot_share_link",
22068
22673
  "Create a shareable load link for one of the agency's snapshots (returns a gohighlevel.com/?share=... URL to import the snapshot into a sub-account). Uses the agency/company-scoped key. WARNING: this is NOT idempotent \u2014 each call mints a NEW link, and there is no API to list or revoke links (revoke in the GHL UI under the snapshot's share settings). Pick share_type deliberately. Use list_snapshots first to get the snapshot id.",
22069
22674
  {
22070
- snapshot_id: import_zod62.z.string().describe("The snapshot id to share (from list_snapshots)."),
22071
- share_type: import_zod62.z.enum(SHARE_TYPES).describe(
22675
+ snapshot_id: import_zod63.z.string().describe("The snapshot id to share (from list_snapshots)."),
22676
+ share_type: import_zod63.z.enum(SHARE_TYPES).describe(
22072
22677
  "Share link type. 'link' = standard share link; 'permanent_link' = non-expiring; 'agency_link' = share to agencies; 'location_link' = load into a sub-account/location; 'marketplace_link' = marketplace listing. No default \u2014 choose intentionally."
22073
22678
  ),
22074
- companyId: import_zod62.z.string().optional().describe(
22679
+ companyId: import_zod63.z.string().optional().describe(
22075
22680
  "Agency/company ID that owns the snapshot. Defaults to the active location's company. Must match the company your agency key is scoped to."
22076
22681
  )
22077
22682
  },
@@ -22099,16 +22704,16 @@ function registerSnapshotTools(server2, client, registry2) {
22099
22704
  }
22100
22705
  );
22101
22706
  }
22102
- var import_zod62, SnapshotSchema, SnapshotsResponseSchema, ShareLinkResponseSchema, SHARE_TYPES;
22707
+ var import_zod63, SnapshotSchema, SnapshotsResponseSchema, ShareLinkResponseSchema, SHARE_TYPES;
22103
22708
  var init_snapshots = __esm({
22104
22709
  "src/tools/snapshots.ts"() {
22105
22710
  "use strict";
22106
- import_zod62 = require("zod");
22711
+ import_zod63 = require("zod");
22107
22712
  init_ghl_client();
22108
22713
  init_tool_helpers();
22109
- SnapshotSchema = import_zod62.z.object({ id: import_zod62.z.string(), name: import_zod62.z.string(), type: import_zod62.z.string() }).passthrough();
22110
- SnapshotsResponseSchema = import_zod62.z.object({ snapshots: import_zod62.z.array(SnapshotSchema) }).passthrough();
22111
- ShareLinkResponseSchema = import_zod62.z.object({ id: import_zod62.z.string(), shareLink: import_zod62.z.string() }).passthrough();
22714
+ SnapshotSchema = import_zod63.z.object({ id: import_zod63.z.string(), name: import_zod63.z.string(), type: import_zod63.z.string() }).passthrough();
22715
+ SnapshotsResponseSchema = import_zod63.z.object({ snapshots: import_zod63.z.array(SnapshotSchema) }).passthrough();
22716
+ ShareLinkResponseSchema = import_zod63.z.object({ id: import_zod63.z.string(), shareLink: import_zod63.z.string() }).passthrough();
22112
22717
  SHARE_TYPES = [
22113
22718
  "link",
22114
22719
  "permanent_link",
@@ -22126,7 +22731,7 @@ function registerPhoneTools(server2, client) {
22126
22731
  "list_phone_numbers",
22127
22732
  "List the LC Phone numbers provisioned for a location (sid, number, label). Read-only. Use to verify or count purchased numbers (e.g. provisioning step 11). Number purchase is not exposed (billable write).",
22128
22733
  {
22129
- locationId: import_zod63.z.string().optional().describe("Defaults to the active location.")
22734
+ locationId: import_zod64.z.string().optional().describe("Defaults to the active location.")
22130
22735
  },
22131
22736
  async ({ locationId: locationId2 }) => {
22132
22737
  const loc = client.resolveLocationId(locationId2);
@@ -22144,7 +22749,7 @@ function registerPhoneTools(server2, client) {
22144
22749
  "list_number_pools",
22145
22750
  "List LC Phone number pools configured for a location. Read-only.",
22146
22751
  {
22147
- locationId: import_zod63.z.string().optional().describe("Defaults to the active location.")
22752
+ locationId: import_zod64.z.string().optional().describe("Defaults to the active location.")
22148
22753
  },
22149
22754
  async ({ locationId: locationId2 }) => {
22150
22755
  const loc = client.resolveLocationId(locationId2);
@@ -22154,15 +22759,15 @@ function registerPhoneTools(server2, client) {
22154
22759
  }
22155
22760
  );
22156
22761
  }
22157
- var import_zod63, PhoneNumberSchema, NumbersResponseSchema, PoolsResponseSchema;
22762
+ var import_zod64, PhoneNumberSchema, NumbersResponseSchema, PoolsResponseSchema;
22158
22763
  var init_phone = __esm({
22159
22764
  "src/tools/phone.ts"() {
22160
22765
  "use strict";
22161
- import_zod63 = require("zod");
22766
+ import_zod64 = require("zod");
22162
22767
  init_tool_helpers();
22163
- PhoneNumberSchema = import_zod63.z.object({ sid: import_zod63.z.string(), value: import_zod63.z.string(), title: import_zod63.z.string().optional() }).passthrough();
22164
- NumbersResponseSchema = import_zod63.z.object({ phoneNumbers: import_zod63.z.array(PhoneNumberSchema) }).passthrough();
22165
- PoolsResponseSchema = import_zod63.z.object({ pools: import_zod63.z.array(import_zod63.z.object({}).passthrough()) }).passthrough();
22768
+ PhoneNumberSchema = import_zod64.z.object({ sid: import_zod64.z.string(), value: import_zod64.z.string(), title: import_zod64.z.string().optional() }).passthrough();
22769
+ NumbersResponseSchema = import_zod64.z.object({ phoneNumbers: import_zod64.z.array(PhoneNumberSchema) }).passthrough();
22770
+ PoolsResponseSchema = import_zod64.z.object({ pools: import_zod64.z.array(import_zod64.z.object({}).passthrough()) }).passthrough();
22166
22771
  }
22167
22772
  });
22168
22773
 
@@ -22180,8 +22785,8 @@ function registerAccountHealthTools(server2, client) {
22180
22785
  "get_account_health_summary",
22181
22786
  "Account-health summary for a location, composed from existing reads (GHL has no reporting API). Returns: total contacts + NEW contacts in the window; total opportunities + counts by status (open/won/lost/abandoned); total conversations; phone-number count. Every metric is explicitly labeled all_time vs window (with start/end) \u2014 windowed and all-time numbers are never conflated. Sections that can't be read return status:'unavailable' (never a misleading 0). Revenue and appointments are intentionally excluded (not reachable / too costly via the public API).",
22182
22787
  {
22183
- locationId: import_zod64.z.string().optional().describe("Defaults to the active location."),
22184
- windowDays: import_zod64.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
22788
+ locationId: import_zod65.z.string().optional().describe("Defaults to the active location."),
22789
+ windowDays: import_zod65.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
22185
22790
  },
22186
22791
  async ({ locationId: locationId2, windowDays }) => {
22187
22792
  const loc = client.resolveLocationId(locationId2);
@@ -22245,16 +22850,16 @@ function registerAccountHealthTools(server2, client) {
22245
22850
  }
22246
22851
  );
22247
22852
  }
22248
- var import_zod64, MetaTotalSchema, TotalSchema2, NumbersSchema, OPP_STATUSES;
22853
+ var import_zod65, MetaTotalSchema, TotalSchema2, NumbersSchema, OPP_STATUSES;
22249
22854
  var init_account_health = __esm({
22250
22855
  "src/tools/account-health.ts"() {
22251
22856
  "use strict";
22252
- import_zod64 = require("zod");
22857
+ import_zod65 = require("zod");
22253
22858
  init_tool_helpers();
22254
22859
  init_contact_window();
22255
- MetaTotalSchema = import_zod64.z.object({ meta: import_zod64.z.object({ total: import_zod64.z.number() }).passthrough() }).passthrough();
22256
- TotalSchema2 = import_zod64.z.object({ total: import_zod64.z.number() }).passthrough();
22257
- NumbersSchema = import_zod64.z.object({ phoneNumbers: import_zod64.z.array(import_zod64.z.unknown()) }).passthrough();
22860
+ MetaTotalSchema = import_zod65.z.object({ meta: import_zod65.z.object({ total: import_zod65.z.number() }).passthrough() }).passthrough();
22861
+ TotalSchema2 = import_zod65.z.object({ total: import_zod65.z.number() }).passthrough();
22862
+ NumbersSchema = import_zod65.z.object({ phoneNumbers: import_zod65.z.array(import_zod65.z.unknown()) }).passthrough();
22258
22863
  OPP_STATUSES = ["open", "won", "lost", "abandoned"];
22259
22864
  }
22260
22865
  });
@@ -22729,11 +23334,11 @@ var init_customization = __esm({
22729
23334
 
22730
23335
  // src/intake-overlay.ts
22731
23336
  function overlayPath() {
22732
- return path15.join(appDataDir(), "intake-overlay.json");
23337
+ return path16.join(appDataDir(), "intake-overlay.json");
22733
23338
  }
22734
23339
  function readOverlay() {
22735
23340
  try {
22736
- const raw = JSON.parse(fs16.readFileSync(overlayPath(), "utf8"));
23341
+ const raw = JSON.parse(fs17.readFileSync(overlayPath(), "utf8"));
22737
23342
  if (raw && typeof raw === "object") return raw;
22738
23343
  } catch {
22739
23344
  }
@@ -22741,14 +23346,14 @@ function readOverlay() {
22741
23346
  }
22742
23347
  function writeOverlay(layer) {
22743
23348
  ensureAppDataDir();
22744
- fs16.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
23349
+ fs17.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
22745
23350
  }
22746
- var fs16, path15;
23351
+ var fs17, path16;
22747
23352
  var init_intake_overlay = __esm({
22748
23353
  "src/intake-overlay.ts"() {
22749
23354
  "use strict";
22750
- fs16 = __toESM(require("fs"));
22751
- path15 = __toESM(require("path"));
23355
+ fs17 = __toESM(require("fs"));
23356
+ path16 = __toESM(require("path"));
22752
23357
  init_credentials_store();
22753
23358
  }
22754
23359
  });
@@ -22810,11 +23415,11 @@ function validateBrief(input) {
22810
23415
  warnings: []
22811
23416
  };
22812
23417
  }
22813
- var import_zod65, BRIEF_SCHEMA_VERSION, PRESETS, presetSchema, BRIEF_SOURCES, briefSourceSchema, pricePointSchema, staffMemberSchema, BRIEF_CALENDAR_TYPES, briefCalendarSchema, TRI_STATES, briefSchema;
23418
+ var import_zod66, BRIEF_SCHEMA_VERSION, PRESETS, presetSchema, BRIEF_SOURCES, briefSourceSchema, pricePointSchema, staffMemberSchema, BRIEF_CALENDAR_TYPES, briefCalendarSchema, TRI_STATES, briefSchema;
22814
23419
  var init_brief = __esm({
22815
23420
  "src/intake-to-build/brief.ts"() {
22816
23421
  "use strict";
22817
- import_zod65 = require("zod");
23422
+ import_zod66 = require("zod");
22818
23423
  BRIEF_SCHEMA_VERSION = "0.1";
22819
23424
  PRESETS = [
22820
23425
  "generic",
@@ -22824,105 +23429,105 @@ var init_brief = __esm({
22824
23429
  "ecom",
22825
23430
  "agency"
22826
23431
  ];
22827
- presetSchema = import_zod65.z.enum(PRESETS);
23432
+ presetSchema = import_zod66.z.enum(PRESETS);
22828
23433
  BRIEF_SOURCES = ["agency_os", "business_os", "intake_form", "hybrid"];
22829
- briefSourceSchema = import_zod65.z.enum(BRIEF_SOURCES);
22830
- pricePointSchema = import_zod65.z.object({
22831
- name: import_zod65.z.string(),
22832
- price: import_zod65.z.string()
23434
+ briefSourceSchema = import_zod66.z.enum(BRIEF_SOURCES);
23435
+ pricePointSchema = import_zod66.z.object({
23436
+ name: import_zod66.z.string(),
23437
+ price: import_zod66.z.string()
22833
23438
  });
22834
- staffMemberSchema = import_zod65.z.object({
22835
- name: import_zod65.z.string().min(1),
22836
- email: import_zod65.z.string().min(1),
23439
+ staffMemberSchema = import_zod66.z.object({
23440
+ name: import_zod66.z.string().min(1),
23441
+ email: import_zod66.z.string().min(1),
22837
23442
  /** Job title as the client wrote it ("Front desk", "Provider"); omitted when
22838
23443
  * the line had none. The plan decides admin/user from it. */
22839
- role: import_zod65.z.string().optional(),
22840
- mobile: import_zod65.z.string().optional()
23444
+ role: import_zod66.z.string().optional(),
23445
+ mobile: import_zod66.z.string().optional()
22841
23446
  });
22842
23447
  BRIEF_CALENDAR_TYPES = ["one_on_one", "round_robin", "class"];
22843
- briefCalendarSchema = import_zod65.z.object({
22844
- name: import_zod65.z.string().min(1),
22845
- type: import_zod65.z.enum(BRIEF_CALENDAR_TYPES),
22846
- staffNames: import_zod65.z.array(import_zod65.z.string()),
23448
+ briefCalendarSchema = import_zod66.z.object({
23449
+ name: import_zod66.z.string().min(1),
23450
+ type: import_zod66.z.enum(BRIEF_CALENDAR_TYPES),
23451
+ staffNames: import_zod66.z.array(import_zod66.z.string()),
22847
23452
  /** Appointment length in minutes, when the client said one ("15 minutes").
22848
23453
  * Lands on the plan calendar as slotDuration (finding 25). */
22849
- durationMinutes: import_zod65.z.number().int().positive().optional()
23454
+ durationMinutes: import_zod66.z.number().int().positive().optional()
22850
23455
  });
22851
23456
  TRI_STATES = ["yes", "no", "unsure"];
22852
- briefSchema = import_zod65.z.object({
22853
- schemaVersion: import_zod65.z.string(),
22854
- briefId: import_zod65.z.string(),
23457
+ briefSchema = import_zod66.z.object({
23458
+ schemaVersion: import_zod66.z.string(),
23459
+ briefId: import_zod66.z.string(),
22855
23460
  preset: presetSchema,
22856
23461
  briefSource: briefSourceSchema,
22857
23462
  /** Partner-OS deep structures (ICA / offer / brand-DNA), carried verbatim. */
22858
- extended: import_zod65.z.record(import_zod65.z.unknown()).optional(),
22859
- business: import_zod65.z.object({
22860
- name: import_zod65.z.string(),
22861
- type: import_zod65.z.string().optional(),
22862
- website: import_zod65.z.string().optional(),
22863
- location: import_zod65.z.string().optional(),
22864
- timezone: import_zod65.z.string().optional(),
23463
+ extended: import_zod66.z.record(import_zod66.z.unknown()).optional(),
23464
+ business: import_zod66.z.object({
23465
+ name: import_zod66.z.string(),
23466
+ type: import_zod66.z.string().optional(),
23467
+ website: import_zod66.z.string().optional(),
23468
+ location: import_zod66.z.string().optional(),
23469
+ timezone: import_zod66.z.string().optional(),
22865
23470
  // Ratified additions (atlas 2026-06-15). Enum-ish but kept as strings for
22866
23471
  // the same tolerance reason as business.type (don't reject valid briefs).
22867
- teamSize: import_zod65.z.string().optional(),
22868
- monthlyLeadVolume: import_zod65.z.string().optional(),
22869
- hours: import_zod65.z.string().optional()
23472
+ teamSize: import_zod66.z.string().optional(),
23473
+ monthlyLeadVolume: import_zod66.z.string().optional(),
23474
+ hours: import_zod66.z.string().optional()
22870
23475
  }).passthrough(),
22871
- offer: import_zod65.z.object({
22872
- summary: import_zod65.z.string().optional(),
23476
+ offer: import_zod66.z.object({
23477
+ summary: import_zod66.z.string().optional(),
22873
23478
  // Parsed best-effort; tolerate a raw string when parsing was not possible.
22874
- pricePoints: import_zod65.z.union([import_zod65.z.array(pricePointSchema), import_zod65.z.string()]).optional(),
22875
- leadMagnet: import_zod65.z.string().optional(),
22876
- avgDealValue: import_zod65.z.string().optional()
23479
+ pricePoints: import_zod66.z.union([import_zod66.z.array(pricePointSchema), import_zod66.z.string()]).optional(),
23480
+ leadMagnet: import_zod66.z.string().optional(),
23481
+ avgDealValue: import_zod66.z.string().optional()
22877
23482
  }).passthrough().optional(),
22878
- audience: import_zod65.z.object({
22879
- ideal: import_zod65.z.string().optional(),
22880
- painPoints: import_zod65.z.array(import_zod65.z.string()).optional(),
22881
- objections: import_zod65.z.array(import_zod65.z.string()).optional()
23483
+ audience: import_zod66.z.object({
23484
+ ideal: import_zod66.z.string().optional(),
23485
+ painPoints: import_zod66.z.array(import_zod66.z.string()).optional(),
23486
+ objections: import_zod66.z.array(import_zod66.z.string()).optional()
22882
23487
  }).passthrough().optional(),
22883
- goal: import_zod65.z.object({
23488
+ goal: import_zod66.z.object({
22884
23489
  // Kept as string: the form option labels are the canonical values, but
22885
23490
  // the contract sample shortens some (e.g. "high-touch"). See §7 note.
22886
- primary: import_zod65.z.string(),
22887
- salesStages: import_zod65.z.array(import_zod65.z.string()).optional(),
22888
- bookingNeeded: import_zod65.z.boolean().optional(),
22889
- followUpStyle: import_zod65.z.string().optional()
23491
+ primary: import_zod66.z.string(),
23492
+ salesStages: import_zod66.z.array(import_zod66.z.string()).optional(),
23493
+ bookingNeeded: import_zod66.z.boolean().optional(),
23494
+ followUpStyle: import_zod66.z.string().optional()
22890
23495
  }).passthrough(),
22891
- channels: import_zod65.z.object({
22892
- email: import_zod65.z.boolean().optional(),
22893
- sms: import_zod65.z.boolean().optional(),
22894
- a2pStatus: import_zod65.z.string().optional(),
22895
- payment: import_zod65.z.string().optional(),
22896
- calendarConnected: import_zod65.z.boolean().optional(),
22897
- social: import_zod65.z.array(import_zod65.z.string()).optional(),
23496
+ channels: import_zod66.z.object({
23497
+ email: import_zod66.z.boolean().optional(),
23498
+ sms: import_zod66.z.boolean().optional(),
23499
+ a2pStatus: import_zod66.z.string().optional(),
23500
+ payment: import_zod66.z.string().optional(),
23501
+ calendarConnected: import_zod66.z.boolean().optional(),
23502
+ social: import_zod66.z.array(import_zod66.z.string()).optional(),
22898
23503
  /** "Do you already have a phone number in GoHighLevel?" */
22899
- hasPhoneNumber: import_zod65.z.enum(TRI_STATES).optional()
23504
+ hasPhoneNumber: import_zod66.z.enum(TRI_STATES).optional()
22900
23505
  }).passthrough().optional(),
22901
23506
  /** Section G — who is on the account and who gets pinged. */
22902
- team: import_zod65.z.object({
22903
- staff: import_zod65.z.array(staffMemberSchema).optional(),
23507
+ team: import_zod66.z.object({
23508
+ staff: import_zod66.z.array(staffMemberSchema).optional(),
22904
23509
  /** Who is notified about new leads: a staff name, or "the owner". */
22905
- notifyName: import_zod65.z.string().optional(),
23510
+ notifyName: import_zod66.z.string().optional(),
22906
23511
  /** Who takes booking / follow-up calls: a staff name, or "the owner". */
22907
- callsName: import_zod65.z.string().optional()
23512
+ callsName: import_zod66.z.string().optional()
22908
23513
  }).passthrough().optional(),
22909
23514
  /** Section H — every booking calendar the client asked for. */
22910
- calendars: import_zod65.z.array(briefCalendarSchema).optional(),
23515
+ calendars: import_zod66.z.array(briefCalendarSchema).optional(),
22911
23516
  /** Section I — copy inputs, verbatim. */
22912
- voice: import_zod65.z.object({
22913
- threeWords: import_zod65.z.string().optional(),
22914
- signatureLine: import_zod65.z.string().optional()
23517
+ voice: import_zod66.z.object({
23518
+ threeWords: import_zod66.z.string().optional(),
23519
+ signatureLine: import_zod66.z.string().optional()
22915
23520
  }).passthrough().optional(),
22916
- assets: import_zod65.z.object({
22917
- existingPipeline: import_zod65.z.string().optional(),
22918
- existingWorkflows: import_zod65.z.string().optional(),
22919
- brand: import_zod65.z.string().optional(),
22920
- notes: import_zod65.z.string().optional()
23521
+ assets: import_zod66.z.object({
23522
+ existingPipeline: import_zod66.z.string().optional(),
23523
+ existingWorkflows: import_zod66.z.string().optional(),
23524
+ brand: import_zod66.z.string().optional(),
23525
+ notes: import_zod66.z.string().optional()
22921
23526
  }).passthrough().optional(),
22922
- flags: import_zod65.z.array(import_zod65.z.string()).optional(),
23527
+ flags: import_zod66.z.array(import_zod66.z.string()).optional(),
22923
23528
  /** Parse-time notes from the normalizer (a staff line it could not read,
22924
23529
  * a calendar type it had to assume). Never fatal; surfaced by validate_brief. */
22925
- warnings: import_zod65.z.array(import_zod65.z.string()).optional()
23530
+ warnings: import_zod66.z.array(import_zod66.z.string()).optional()
22926
23531
  }).strict();
22927
23532
  }
22928
23533
  });
@@ -23287,9 +23892,9 @@ function presetForBusinessType(type) {
23287
23892
  return "generic";
23288
23893
  }
23289
23894
  }
23290
- function setPath(target, path26, value) {
23895
+ function setPath(target, path27, value) {
23291
23896
  if (value === void 0) return;
23292
- const parts = path26.split(".");
23897
+ const parts = path27.split(".");
23293
23898
  let node = target;
23294
23899
  for (let i = 0; i < parts.length - 1; i++) {
23295
23900
  const k = parts[i];
@@ -23456,18 +24061,18 @@ var init_normalizer = __esm({
23456
24061
 
23457
24062
  // src/plan-store.ts
23458
24063
  function plansDir(base = appDataDir()) {
23459
- return path16.join(base, "plans");
24064
+ return path17.join(base, "plans");
23460
24065
  }
23461
24066
  function savePlanRecord(rec, base) {
23462
24067
  const dir = plansDir(base);
23463
- fs17.mkdirSync(dir, { recursive: true, mode: 448 });
23464
- const file = path16.join(dir, `${rec.locationId}.json`);
23465
- fs17.writeFileSync(file, JSON.stringify(rec, null, 2), { mode: 384 });
24068
+ fs18.mkdirSync(dir, { recursive: true, mode: 448 });
24069
+ const file = path17.join(dir, `${rec.locationId}.json`);
24070
+ fs18.writeFileSync(file, JSON.stringify(rec, null, 2), { mode: 384 });
23466
24071
  return file;
23467
24072
  }
23468
24073
  function readSavedPlan(locationId2, base) {
23469
24074
  try {
23470
- return JSON.parse(fs17.readFileSync(path16.join(plansDir(base), `${locationId2}.json`), "utf8"));
24075
+ return JSON.parse(fs18.readFileSync(path17.join(plansDir(base), `${locationId2}.json`), "utf8"));
23471
24076
  } catch {
23472
24077
  return null;
23473
24078
  }
@@ -23490,24 +24095,24 @@ function planSummary(rec) {
23490
24095
  if (wfs.length) parts.push(`workflows: ${wfs.map((w) => `${w.name} (${Array.isArray(w.actions) ? w.actions.length : "?"} actions)`).join("; ")}`);
23491
24096
  return parts.join("\n") || "(plan has no recognisable sections)";
23492
24097
  }
23493
- var fs17, path16;
24098
+ var fs18, path17;
23494
24099
  var init_plan_store = __esm({
23495
24100
  "src/plan-store.ts"() {
23496
24101
  "use strict";
23497
- fs17 = __toESM(require("fs"));
23498
- path16 = __toESM(require("path"));
24102
+ fs18 = __toESM(require("fs"));
24103
+ path17 = __toESM(require("path"));
23499
24104
  init_credentials_store();
23500
24105
  }
23501
24106
  });
23502
24107
 
23503
24108
  // src/intake-to-build/revert.ts
23504
- function plainDate(iso, timeZone) {
23505
- const d = new Date(iso);
23506
- if (Number.isNaN(d.getTime())) return iso;
24109
+ function plainDate(iso2, timeZone) {
24110
+ const d = new Date(iso2);
24111
+ if (Number.isNaN(d.getTime())) return iso2;
23507
24112
  try {
23508
24113
  return new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone }).format(d);
23509
24114
  } catch {
23510
- return iso.slice(0, 10);
24115
+ return iso2.slice(0, 10);
23511
24116
  }
23512
24117
  }
23513
24118
  function cannotReasonForType(type) {
@@ -24412,17 +25017,17 @@ function planFromPreset(input) {
24412
25017
  }
24413
25018
  function presetsRoot() {
24414
25019
  const candidates = [
24415
- path17.join(__dirname, "..", "skills", "blueprint", "presets"),
25020
+ path18.join(__dirname, "..", "skills", "blueprint", "presets"),
24416
25021
  // dist/ → package root
24417
- path17.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
25022
+ path18.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
24418
25023
  // src/intake-to-build in dev
24419
- path17.join(process.cwd(), "skills", "blueprint", "presets")
25024
+ path18.join(process.cwd(), "skills", "blueprint", "presets")
24420
25025
  ];
24421
- return candidates.find((c) => fs18.existsSync(c)) ?? null;
25026
+ return candidates.find((c) => fs19.existsSync(c)) ?? null;
24422
25027
  }
24423
25028
  function readPresetFile(dir, base) {
24424
25029
  try {
24425
- const raw = JSON.parse(fs18.readFileSync(path17.join(dir, `${base}.preset.json`), "utf8"));
25030
+ const raw = JSON.parse(fs19.readFileSync(path18.join(dir, `${base}.preset.json`), "utf8"));
24426
25031
  if (raw && typeof raw === "object" && typeof raw.presetId === "string" && raw.skeleton) return raw;
24427
25032
  return null;
24428
25033
  } catch {
@@ -24434,7 +25039,7 @@ function loadPresetChoice(requested, briefPreset) {
24434
25039
  if (!dir) {
24435
25040
  return { ok: false, error: "The preset library is not installed with this copy of the tool (skills/blueprint/presets is missing) \u2014 reinstall, or author the plan by hand." };
24436
25041
  }
24437
- const bases = fs18.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
25042
+ const bases = fs19.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
24438
25043
  const tryMatch = (want) => {
24439
25044
  const norm4 = slugify(want);
24440
25045
  for (const base of bases) {
@@ -24465,12 +25070,12 @@ function loadPresetChoice(requested, briefPreset) {
24465
25070
  if (generic) return { ok: true, preset: generic, file: "generic-client.preset.json", note: "Using the generic preset." };
24466
25071
  return { ok: false, error: "No usable preset file was found in the library." };
24467
25072
  }
24468
- var fs18, path17, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
25073
+ var fs19, path18, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
24469
25074
  var init_plan_from_preset = __esm({
24470
25075
  "src/intake-to-build/plan-from-preset.ts"() {
24471
25076
  "use strict";
24472
- fs18 = __toESM(require("fs"));
24473
- path17 = __toESM(require("path"));
25077
+ fs19 = __toESM(require("fs"));
25078
+ path18 = __toESM(require("path"));
24474
25079
  init_plan();
24475
25080
  init_brief();
24476
25081
  init_normalizer();
@@ -25608,15 +26213,15 @@ ${text2.slice(0, 300)}`);
25608
26213
  };
25609
26214
  }
25610
26215
  function makePipelineApi(builderClient) {
25611
- return async (method, path26, body) => {
26216
+ return async (method, path27, body) => {
25612
26217
  const headers = await builderClient.buildHeaders();
25613
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path26}`;
26218
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path27}`;
25614
26219
  const options = { method, headers };
25615
26220
  if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
25616
26221
  const response = await fetch(url, options);
25617
26222
  if (!response.ok) {
25618
26223
  const text2 = await response.text();
25619
- throw new Error(`Pipeline API ${response.status}: ${method} ${path26}
26224
+ throw new Error(`Pipeline API ${response.status}: ${method} ${path27}
25620
26225
  ${text2.slice(0, 300)}`);
25621
26226
  }
25622
26227
  const text = await response.text();
@@ -25629,17 +26234,17 @@ ${text2.slice(0, 300)}`);
25629
26234
  };
25630
26235
  }
25631
26236
  function makeFunnelApi(builderClient) {
25632
- return async (method, path26, body) => {
26237
+ return async (method, path27, body) => {
25633
26238
  const headers = await builderClient.buildHeaders();
25634
26239
  headers.Origin = "https://app.gohighlevel.com";
25635
26240
  headers.Referer = "https://app.gohighlevel.com/";
25636
- const url = `https://backend.leadconnectorhq.com/funnels${path26}`;
26241
+ const url = `https://backend.leadconnectorhq.com/funnels${path27}`;
25637
26242
  const options = { method, headers };
25638
26243
  if (body && (method === "POST" || method === "PUT")) options.body = JSON.stringify(body);
25639
26244
  const response = await fetch(url, options);
25640
26245
  if (!response.ok) {
25641
26246
  const text2 = await response.text();
25642
- throw new Error(`Funnel API ${response.status}: ${method} ${path26}
26247
+ throw new Error(`Funnel API ${response.status}: ${method} ${path27}
25643
26248
  ${text2.slice(0, 300)}`);
25644
26249
  }
25645
26250
  const text = await response.text();
@@ -26011,7 +26616,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26011
26616
  "get_intake_question_set",
26012
26617
  `Return the Intake-to-Build question set (the questions the installed intake form asks) plus each question's GHL field mapping and the Brief field it feeds. Read-only. Pass industry (e.g. "clinic", "med-spa") to see the tailored set \u2014 base questions + that industry's pack + the agency's own overlay, exactly what install_intake_form installs for that industry; omit it for the base set. Use this to review or render the intake before installing it.`,
26013
26618
  {
26014
- industry: import_zod66.z.string().optional().describe(`Industry pack slug to compose in (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set.`)
26619
+ industry: import_zod67.z.string().optional().describe(`Industry pack slug to compose in (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set.`)
26015
26620
  },
26016
26621
  async ({ industry }) => {
26017
26622
  const agency = readOverlay();
@@ -26042,7 +26647,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26042
26647
  "validate_brief",
26043
26648
  "Validate an Intake-to-Build Brief object against the \xA74 schema. Returns {valid, errors}. Use to check a normalized brief before handing it to the plan-generation skill.",
26044
26649
  {
26045
- brief: import_zod66.z.record(import_zod66.z.unknown()).describe("The Brief object to validate.")
26650
+ brief: import_zod67.z.record(import_zod67.z.unknown()).describe("The Brief object to validate.")
26046
26651
  },
26047
26652
  async ({ brief }) => validateBrief(brief)
26048
26653
  );
@@ -26051,7 +26656,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26051
26656
  "validate_build_plan",
26052
26657
  "Validate an Intake-to-Build Build Plan against the \xA75 schema AND check ref-integrity: every symbolic ref (pipeline.*, stage.*, tag.*, ...) must resolve to a defined object of the right type, and refs must be unique. Dead references are reported as errors here, before any build runs \u2014 the structural guard against 'an invalid ID silently kills downstream actions'. Returns {valid, errors, warnings, referencesScanned}.",
26053
26658
  {
26054
- plan: import_zod66.z.record(import_zod66.z.unknown()).describe("The Build Plan object to validate.")
26659
+ plan: import_zod67.z.record(import_zod67.z.unknown()).describe("The Build Plan object to validate.")
26055
26660
  },
26056
26661
  async ({ plan }) => validateBuildPlan(plan)
26057
26662
  );
@@ -26059,25 +26664,25 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26059
26664
  "apply_build_plan",
26060
26665
  'Take an APPROVED Blueprint \xA75 build plan + the CURRENT confirmed sub-account and build it. PREFERRED for a fresh build: pass fromPreset:true + the \xA74 brief and the plan is COMPOSED SERVER-SIDE from the industry preset in milliseconds (structure, workflows, send-ready copy token-filled from the brief) \u2014 never author 100 workflow actions by hand when a preset covers the brief; see the fromPreset parameter. mode:"dry_run" (default) writes NOTHING \u2014 it resolves refs, expands each workflow\'s logical actions to native GHL JSON, runs the NEVER-CLOBBER existing-asset scan, and returns a two-part report. Run it FIRST. mode:"execute" performs LIVE writes for the CRM backbone (pipelines+stages, custom fields, tags, custom values), calendars, AND forms: never clobbers (same-named objects are bound to their existing id, never modified), verifies each create by read-back before resolving its ref, halts on the first failure returning the partial idMap, and is idempotent (re-run = no-op). Staff-requiring calendars (round_robin etc.) auto-build only when you are the sole account user (auto-assigns you as the team member); with 0 or 2+ users they\'re surfaced as a manual step, not auto-staffed to a guess. CALENDAR TEAM: after creating any calendar that names staff, the team is READ BACK \u2014 a 200 is not evidence. A one-off `event` calendar has no team-member concept (GHL accepts teamMembers and drops them silently), so a calendar naming staff is never built as one, and if the people still do not land the run reports it AND emits a manual step naming the person and the calendar (manual type `calendar_team`) \u2014 never a silent success. Forms build with their standard + custom fields (custom fieldRefs resolve to the real fields created earlier in the run). Funnels: a GHL funnel (target:"ghl", default) builds structurally (funnel + named steps; page content/HTML is a manual step \u2014 plans carry outlines); a funnel with target:"external" is NOT built or deployed here \u2014 the user builds + hosts the site themselves (Cloudflare/Vercel) and wires its form back to this GHL sub-account (surfaced as a manual wiring step). Workflows build as DRAFT with all their logical actions expanded to native GHL JSON (incl. opportunity create/move steps, re-enabled v3.41.0) and chained; a contact_tag trigger is built automatically, other trigger types are surfaced as a manual step; the operator reviews + publishes. STAFF (Tier 1 v2): plan.users are created through the agency key (the create_user path \u2014 bound by email when they already exist, no invented password) BEFORE calendars and workflows, so calendars get their named team members (teamMemberRefs) and notification / task steps target real user ids (userRef). Without an agency key the run still proceeds: those users are pending, and every step that notifies them is STILL BUILT with a placeholder target and flagged staff_pending ("Add a staff member, then assign this step") \u2014 never dropped, never pointed at a user from another account. TEMPLATES: plan.templates (emails + sms) are saved as account-level email templates / SMS snippets before the workflows (never-clobber by name); the steps keep the same copy inline and the template ids come back in `templates`. Email templates are created in the editor\'s own format (vibe-editor) so they open and edit in Marketing \u2192 Emails \u2192 Templates, and each saved body is READ BACK from GHL\'s preview \u2014 a template whose copy is not seen there is reported with a warning and counted in templatesUnverified, never claimed as written. Always confirms the active location and validates the plan before any write. RE-RUNS: pass useSavedPlan:true (no plan) to build from the plan saved on this machine by the earlier execute; a differently named plan is refused while a saved one exists unless replaceSavedPlan:true \u2014 re-authoring a plan on a re-run drifts and duplicates objects (a second pipeline beside the first).',
26061
26666
  {
26062
- plan: import_zod66.z.record(import_zod66.z.unknown()).optional().describe("The approved \xA75 Build Plan object. Omit it and pass useSavedPlan:true to build from the plan already saved for this account."),
26063
- fromPreset: import_zod66.z.boolean().optional().describe("PLAN-FROM-PRESET (PRD row 4d): compose the \xA75 plan SERVER-SIDE from the industry preset + the \xA74 brief, deterministically, in milliseconds \u2014 never author workflow actions by hand when a preset covers the brief. Pass `brief` (required) and optionally `preset` and `copy`; omit `plan`. The composed plan carries the preset's full structure (workflows, templates with complete send-ready copy token-filled from the brief, staff users, calendars with the brief's appointment lengths, pipeline stages, notification targets) and then flows through the exact same validate \u2192 dry_run/execute path as a passed plan, including being saved on execute. The dry_run response lists copySlots \u2014 the template refs whose subject/body the model MAY refine via `copy` on the execute call. If the preset cannot cover the brief the tool answers phase:\"compose\" with the reasons; only then author a plan by hand."),
26064
- brief: import_zod66.z.record(import_zod66.z.unknown()).optional().describe("fromPreset only: the \xA74 Brief (from normalize_submission_to_brief, or assembled from the cockpit intake answers)."),
26065
- preset: import_zod66.z.string().optional().describe("fromPreset only: which preset to compose from \u2014 a preset id (med_spa, clinic, coach, local_service, ecom, generic), an alias (dental, medspa, home_services\u2026), or an industry slug (med-spa, local-service, ecommerce\u2026). Defaults to the brief's own preset field; an unknown value falls back to the generic preset (noted in the response)."),
26066
- copy: import_zod66.z.array(
26067
- import_zod66.z.object({
26068
- ref: import_zod66.z.string().describe("email_template.* or sms_template.* ref from the composed plan"),
26069
- subject: import_zod66.z.string().optional(),
26070
- html: import_zod66.z.string().optional(),
26071
- body: import_zod66.z.string().optional()
26667
+ plan: import_zod67.z.record(import_zod67.z.unknown()).optional().describe("The approved \xA75 Build Plan object. Omit it and pass useSavedPlan:true to build from the plan already saved for this account."),
26668
+ fromPreset: import_zod67.z.boolean().optional().describe("PLAN-FROM-PRESET (PRD row 4d): compose the \xA75 plan SERVER-SIDE from the industry preset + the \xA74 brief, deterministically, in milliseconds \u2014 never author workflow actions by hand when a preset covers the brief. Pass `brief` (required) and optionally `preset` and `copy`; omit `plan`. The composed plan carries the preset's full structure (workflows, templates with complete send-ready copy token-filled from the brief, staff users, calendars with the brief's appointment lengths, pipeline stages, notification targets) and then flows through the exact same validate \u2192 dry_run/execute path as a passed plan, including being saved on execute. The dry_run response lists copySlots \u2014 the template refs whose subject/body the model MAY refine via `copy` on the execute call. If the preset cannot cover the brief the tool answers phase:\"compose\" with the reasons; only then author a plan by hand."),
26669
+ brief: import_zod67.z.record(import_zod67.z.unknown()).optional().describe("fromPreset only: the \xA74 Brief (from normalize_submission_to_brief, or assembled from the cockpit intake answers)."),
26670
+ preset: import_zod67.z.string().optional().describe("fromPreset only: which preset to compose from \u2014 a preset id (med_spa, clinic, coach, local_service, ecom, generic), an alias (dental, medspa, home_services\u2026), or an industry slug (med-spa, local-service, ecommerce\u2026). Defaults to the brief's own preset field; an unknown value falls back to the generic preset (noted in the response)."),
26671
+ copy: import_zod67.z.array(
26672
+ import_zod67.z.object({
26673
+ ref: import_zod67.z.string().describe("email_template.* or sms_template.* ref from the composed plan"),
26674
+ subject: import_zod67.z.string().optional(),
26675
+ html: import_zod67.z.string().optional(),
26676
+ body: import_zod67.z.string().optional()
26072
26677
  })
26073
26678
  ).optional().describe("fromPreset only: client-specific copy overrides \u2014 subject/html for an email template, body for an SMS template. Copy ONLY; structure is never overridable. Overrides land on the template AND its reviewable asset. An unknown ref is an error, never a silent no-op."),
26074
- useSavedPlan: import_zod66.z.boolean().optional().describe("Load the approved plan saved for the active location by an earlier execute (plans/<locationId>.json on this machine) instead of passing one. The right choice for ANY re-run: re-authoring a plan drifts and duplicates objects."),
26075
- replaceSavedPlan: import_zod66.z.boolean().optional().describe("Only with a new `plan` whose planId differs from the saved one: confirms the operator approved the new plan, so it replaces the saved plan on execute. Without it, a differently named plan is refused while a saved plan exists."),
26076
- mode: import_zod66.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes (not yet enabled)."),
26077
- locationId: import_zod66.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first)."),
26078
- metHandoffs: import_zod66.z.array(import_zod66.z.string()).optional().describe('Handoff refs the operator has already satisfied (e.g. ["handoff.a2p"]) \u2014 lifts their gate so dependent workflows are not held DRAFT.'),
26079
- publishWorkflows: import_zod66.z.boolean().optional().describe("If true, ungated workflows would be published instead of left DRAFT. Default false (DRAFT)."),
26080
- onConflict: import_zod66.z.enum(["skip", "abort"]).optional().describe("skip (default) = bind same-named existing objects and continue. abort = report conflicts as a halt.")
26679
+ useSavedPlan: import_zod67.z.boolean().optional().describe("Load the approved plan saved for the active location by an earlier execute (plans/<locationId>.json on this machine) instead of passing one. The right choice for ANY re-run: re-authoring a plan drifts and duplicates objects."),
26680
+ replaceSavedPlan: import_zod67.z.boolean().optional().describe("Only with a new `plan` whose planId differs from the saved one: confirms the operator approved the new plan, so it replaces the saved plan on execute. Without it, a differently named plan is refused while a saved plan exists."),
26681
+ mode: import_zod67.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes (not yet enabled)."),
26682
+ locationId: import_zod67.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first)."),
26683
+ metHandoffs: import_zod67.z.array(import_zod67.z.string()).optional().describe('Handoff refs the operator has already satisfied (e.g. ["handoff.a2p"]) \u2014 lifts their gate so dependent workflows are not held DRAFT.'),
26684
+ publishWorkflows: import_zod67.z.boolean().optional().describe("If true, ungated workflows would be published instead of left DRAFT. Default false (DRAFT)."),
26685
+ onConflict: import_zod67.z.enum(["skip", "abort"]).optional().describe("skip (default) = bind same-named existing objects and continue. abort = report conflicts as a halt.")
26081
26686
  },
26082
26687
  async ({ plan: planArgIn, fromPreset, brief: briefArg, preset: presetArg, copy: copyArg, useSavedPlan, replaceSavedPlan, mode, locationId: locationId2, metHandoffs, publishWorkflows, onConflict }) => {
26083
26688
  try {
@@ -26360,9 +26965,9 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26360
26965
  "revert_build",
26361
26966
  `Undo a Blueprint build: remove the objects a recorded apply_build_plan run CREATED in the current sub-account, and nothing else. PREVIEWS BY DEFAULT \u2014 with no confirm it writes nothing and returns a plain-English list of exactly what would be removed, grouped, plus what it will leave alone and why. Pass confirm:"DELETE" to actually remove them. Acts on one recorded run: pass runId, or omit it for the most recent run in the ACTIVE account (runs from any other sub-account are in a different journal and can never be reached from here). The one hard rule: it removes only objects the run's never-clobber ledger recorded as CREATED \u2014 anything the build merely BOUND to (a pipeline, tag, calendar or workflow that was already in the account) is never touched, so a build run against an account that already had assets cannot delete the client's own work. Deletes run in the exact reverse of the build order (workflows first, then templates, funnels and their pages, forms, calendars, custom values, tags, custom fields, pipelines) so nothing is removed while something else still points at it. A refusal from GoHighLevel never aborts the run: refusals are collected and reported at the end with what to do about each. After the deletes the account is re-read independently and each object is reported as confirmed gone or as accepted-but-unconfirmed. Idempotent: run it twice and the second run finds nothing left and says so. Two kinds are deliberately NEVER removed automatically \u2014 staff users (a person's login; removing it can strip them off appointments and assignments) and text-message templates (GoHighLevel exposes no delete for them) \u2014 both are listed with where to remove them by hand.`,
26362
26967
  {
26363
- runId: import_zod66.z.string().optional().describe('The recorded build run to undo (the `runId` apply_build_plan returned, e.g. "r-20260827T0142-9c1e"). Omit for the most recent run recorded for the active sub-account.'),
26364
- confirm: import_zod66.z.literal("DELETE").optional().describe('Omit for a preview (no writes at all). Pass "DELETE" to actually remove the objects the preview listed.'),
26365
- locationId: import_zod66.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first).")
26968
+ runId: import_zod67.z.string().optional().describe('The recorded build run to undo (the `runId` apply_build_plan returned, e.g. "r-20260827T0142-9c1e"). Omit for the most recent run recorded for the active sub-account.'),
26969
+ confirm: import_zod67.z.literal("DELETE").optional().describe('Omit for a preview (no writes at all). Pass "DELETE" to actually remove the objects the preview listed.'),
26970
+ locationId: import_zod67.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first).")
26366
26971
  },
26367
26972
  async ({ runId, confirm: confirm2, locationId: locationId2 }) => {
26368
26973
  try {
@@ -26540,10 +27145,10 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26540
27145
  "install_intake_form",
26541
27146
  `Install the Intake-to-Build client-intake form into the CURRENT GHL location (whatever get_current_location returns). Account-agnostic, zero hardcoded IDs. Creates any missing intake custom fields (idempotent \u2014 reused on re-run), then builds the form with the proven GHL form-builder field shapes and verifies it. Pass industry (e.g. "clinic", "med-spa") to install the TAILORED set \u2014 base questions + that industry's pack + the agency's own overlay (added / removed questions), the same set the Command OS cockpit asks \u2014 so no cockpit answer is left without a form field; omit it for the base set. Returns {formId, fieldMap} \u2014 keep fieldMap; normalize_submission_to_brief uses it. Pass dryRun:true to preview what would be created without writing. Pass formId to update an existing intake form in place (e.g. to add an industry's questions to a form installed without one) instead of creating a new one.`,
26542
27147
  {
26543
- dryRun: import_zod66.z.boolean().optional().describe("Preview the fields/form that would be created without writing anything."),
26544
- formId: import_zod66.z.string().optional().describe("Update this existing form in place instead of creating a new one."),
26545
- formName: import_zod66.z.string().optional().describe(`Form name. Defaults to "${INTAKE_FORM_NAME}".`),
26546
- industry: import_zod66.z.string().optional().describe(`Industry pack to install with the base set (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set only.`)
27148
+ dryRun: import_zod67.z.boolean().optional().describe("Preview the fields/form that would be created without writing anything."),
27149
+ formId: import_zod67.z.string().optional().describe("Update this existing form in place instead of creating a new one."),
27150
+ formName: import_zod67.z.string().optional().describe(`Form name. Defaults to "${INTAKE_FORM_NAME}".`),
27151
+ industry: import_zod67.z.string().optional().describe(`Industry pack to install with the base set (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set only.`)
26547
27152
  },
26548
27153
  async ({ dryRun, formId, formName, industry }) => {
26549
27154
  try {
@@ -26646,10 +27251,10 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26646
27251
  "normalize_submission_to_brief",
26647
27252
  'Read an intake form submission and normalize it into a \xA74 Brief (briefSource:"intake_form"). Pass the formId; by default the most recent submission is used (or pass submissionId). The intakeKey->customFieldId map is taken from fieldMap if provided (the install_intake_form output, most robust), otherwise reconstructed from the live form. Returns {brief, validation, submissionId} \u2014 validation flags any missing required fields (e.g. an incomplete submission).',
26648
27253
  {
26649
- formId: import_zod66.z.string().describe("The intake form ID (from install_intake_form)."),
26650
- submissionId: import_zod66.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
26651
- fieldMap: import_zod66.z.record(import_zod66.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
26652
- preset: import_zod66.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
27254
+ formId: import_zod67.z.string().describe("The intake form ID (from install_intake_form)."),
27255
+ submissionId: import_zod67.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
27256
+ fieldMap: import_zod67.z.record(import_zod67.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
27257
+ preset: import_zod67.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
26653
27258
  },
26654
27259
  async ({ formId, submissionId, fieldMap, preset }) => {
26655
27260
  try {
@@ -26678,7 +27283,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26678
27283
  const formFull = await formApiRequest(bc, "GET", `/${formId}?locationId=${locationId2}`);
26679
27284
  resolvedMap = buildFieldMapFromFormFields(extractFormFields(formFull));
26680
27285
  }
26681
- const presetSchema2 = import_zod66.z.enum(["generic", "med_spa", "clinic_launch_a2p", "coach", "ecom", "agency"]).optional();
27286
+ const presetSchema2 = import_zod67.z.enum(["generic", "med_spa", "clinic_launch_a2p", "coach", "ecom", "agency"]).optional();
26682
27287
  const presetParsed = presetSchema2.safeParse(preset);
26683
27288
  const brief = normalizeSubmissionToBrief({
26684
27289
  others,
@@ -26699,11 +27304,11 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
26699
27304
  }
26700
27305
  );
26701
27306
  }
26702
- var import_zod66, customFieldItemSchema, sleep5;
27307
+ var import_zod67, customFieldItemSchema, sleep5;
26703
27308
  var init_intake_to_build = __esm({
26704
27309
  "src/tools/intake-to-build.ts"() {
26705
27310
  "use strict";
26706
- import_zod66 = require("zod");
27311
+ import_zod67 = require("zod");
26707
27312
  init_tool_helpers();
26708
27313
  init_form_builder();
26709
27314
  init_calendars();
@@ -26725,16 +27330,16 @@ var init_intake_to_build = __esm({
26725
27330
  init_plan_from_preset();
26726
27331
  init_executor();
26727
27332
  init_execute();
26728
- customFieldItemSchema = import_zod66.z.object({
26729
- id: import_zod66.z.string(),
26730
- name: import_zod66.z.string(),
26731
- fieldKey: import_zod66.z.string(),
26732
- dataType: import_zod66.z.string(),
26733
- model: import_zod66.z.string().optional(),
26734
- parentId: import_zod66.z.string().optional(),
26735
- position: import_zod66.z.number().optional(),
26736
- dateAdded: import_zod66.z.string().optional(),
26737
- picklistOptions: import_zod66.z.array(import_zod66.z.string()).optional()
27333
+ customFieldItemSchema = import_zod67.z.object({
27334
+ id: import_zod67.z.string(),
27335
+ name: import_zod67.z.string(),
27336
+ fieldKey: import_zod67.z.string(),
27337
+ dataType: import_zod67.z.string(),
27338
+ model: import_zod67.z.string().optional(),
27339
+ parentId: import_zod67.z.string().optional(),
27340
+ position: import_zod67.z.number().optional(),
27341
+ dateAdded: import_zod67.z.string().optional(),
27342
+ picklistOptions: import_zod67.z.array(import_zod67.z.string()).optional()
26738
27343
  }).passthrough();
26739
27344
  sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
26740
27345
  }
@@ -26762,6 +27367,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
26762
27367
  registerUserGuideTools(wrap(USER_GUIDE_MODULE));
26763
27368
  registerAgencyProfileTools(wrap(AGENCY_PROFILE_MODULE));
26764
27369
  registerAssessmentTools(wrap(ASSESSMENT_MODULE), client);
27370
+ registerConnectorTools(wrap(CONNECTORS_MODULE));
26765
27371
  registerClientEngagementTools(wrap(CLIENT_ENGAGEMENTS_MODULE), registry2);
26766
27372
  registerAuditReportTools(wrap(AUDIT_REPORT_MODULE));
26767
27373
  registerDailyBriefTools(wrap(DAILY_BRIEF_MODULE), registry2);
@@ -26810,7 +27416,7 @@ function registerAllTools(server2, client, registry2, mcpVersion, env = process.
26810
27416
  }
26811
27417
  return { registeredTools, gatedTools, planGatedTools };
26812
27418
  }
26813
- var publicApiTools, internalApiTools, VALIDATORS_MODULE, DIAGNOSTICS_MODULE, LOCATION_SWITCHER_MODULE, SNAPSHOTS_MODULE, ACCOUNT_EXPORT_MODULE, FORM_BUILDER_MODULE, INTAKE_TO_BUILD_MODULE, EMAIL_BUILDER_MODULE, FUNNEL_QA_MODULE, USER_GUIDE_MODULE, AGENCY_PROFILE_MODULE, ASSESSMENT_MODULE, CLIENT_ENGAGEMENTS_MODULE, AUDIT_REPORT_MODULE, DAILY_BRIEF_MODULE, CHECKUP_MODULE, KNOWN_MODULES, PUBLIC_API_MODULES, INTERNAL_API_MODULES, DUAL_CLIENT_MODULES;
27419
+ var publicApiTools, internalApiTools, VALIDATORS_MODULE, DIAGNOSTICS_MODULE, LOCATION_SWITCHER_MODULE, SNAPSHOTS_MODULE, ACCOUNT_EXPORT_MODULE, FORM_BUILDER_MODULE, INTAKE_TO_BUILD_MODULE, EMAIL_BUILDER_MODULE, FUNNEL_QA_MODULE, USER_GUIDE_MODULE, AGENCY_PROFILE_MODULE, ASSESSMENT_MODULE, CONNECTORS_MODULE, CLIENT_ENGAGEMENTS_MODULE, AUDIT_REPORT_MODULE, DAILY_BRIEF_MODULE, CHECKUP_MODULE, KNOWN_MODULES, PUBLIC_API_MODULES, INTERNAL_API_MODULES, DUAL_CLIENT_MODULES;
26814
27420
  var init_tools = __esm({
26815
27421
  "src/tools/index.ts"() {
26816
27422
  "use strict";
@@ -26864,6 +27470,7 @@ var init_tools = __esm({
26864
27470
  init_user_guide();
26865
27471
  init_agency_profile2();
26866
27472
  init_assessment();
27473
+ init_connectors();
26867
27474
  init_client_engagements2();
26868
27475
  init_audit_report2();
26869
27476
  init_daily_brief2();
@@ -26935,11 +27542,13 @@ var init_tools = __esm({
26935
27542
  USER_GUIDE_MODULE = "user-guide";
26936
27543
  AGENCY_PROFILE_MODULE = "agency-profile";
26937
27544
  ASSESSMENT_MODULE = "assessment";
27545
+ CONNECTORS_MODULE = "connectors";
26938
27546
  CLIENT_ENGAGEMENTS_MODULE = "client-engagements";
26939
27547
  AUDIT_REPORT_MODULE = "audit-report";
26940
27548
  DAILY_BRIEF_MODULE = "daily-brief";
26941
27549
  CHECKUP_MODULE = "checkup";
26942
27550
  KNOWN_MODULES = /* @__PURE__ */ new Set([
27551
+ "connectors",
26943
27552
  "assessment",
26944
27553
  ...publicApiTools.map(([, label]) => label),
26945
27554
  ...internalApiTools.map(([, label]) => label),
@@ -26960,6 +27569,7 @@ var init_tools = __esm({
26960
27569
  ACCOUNT_EXPORT_MODULE
26961
27570
  ]);
26962
27571
  PUBLIC_API_MODULES = /* @__PURE__ */ new Set([
27572
+ "connectors",
26963
27573
  "assessment",
26964
27574
  ...publicApiTools.map(([, label]) => label),
26965
27575
  USER_GUIDE_MODULE,
@@ -27011,7 +27621,7 @@ function defaultIO() {
27011
27621
  return {
27012
27622
  out: (l) => process.stdout.write(l + "\n"),
27013
27623
  err: (l) => process.stderr.write(l + "\n"),
27014
- rename: (from, to) => fs20.renameSync(from, to),
27624
+ rename: (from, to) => fs21.renameSync(from, to),
27015
27625
  sleep: (ms) => new Promise((r) => setTimeout(r, ms))
27016
27626
  };
27017
27627
  }
@@ -27021,32 +27631,32 @@ function candidateConfigPaths(opts) {
27021
27631
  const file = "claude_desktop_config.json";
27022
27632
  const candidates = [];
27023
27633
  if (platform2 === "darwin") {
27024
- candidates.push(path19.join(home, "Library", "Application Support", "Claude", file));
27634
+ candidates.push(path20.join(home, "Library", "Application Support", "Claude", file));
27025
27635
  } else if (platform2 === "win32") {
27026
- const appData = opts?.appData ?? process.env.APPDATA ?? path19.join(home, "AppData", "Roaming");
27027
- candidates.push(path19.join(appData, "Claude", file));
27028
- const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path19.join(home, "AppData", "Local");
27029
- const packagesDir = path19.join(localAppData, "Packages");
27636
+ const appData = opts?.appData ?? process.env.APPDATA ?? path20.join(home, "AppData", "Roaming");
27637
+ candidates.push(path20.join(appData, "Claude", file));
27638
+ const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path20.join(home, "AppData", "Local");
27639
+ const packagesDir = path20.join(localAppData, "Packages");
27030
27640
  try {
27031
- for (const entry of fs20.readdirSync(packagesDir)) {
27641
+ for (const entry of fs21.readdirSync(packagesDir)) {
27032
27642
  if (entry.startsWith("Claude_")) {
27033
- candidates.push(path19.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file));
27643
+ candidates.push(path20.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file));
27034
27644
  }
27035
27645
  }
27036
27646
  } catch {
27037
27647
  }
27038
27648
  } else {
27039
- candidates.push(path19.join(home, ".config", "Claude", file));
27649
+ candidates.push(path20.join(home, ".config", "Claude", file));
27040
27650
  }
27041
27651
  return candidates;
27042
27652
  }
27043
27653
  function resolveConfigPath(explicitPath) {
27044
27654
  if (explicitPath) {
27045
- const p = path19.resolve(explicitPath);
27046
- return { configPath: p, exists: fs20.existsSync(p) };
27655
+ const p = path20.resolve(explicitPath);
27656
+ return { configPath: p, exists: fs21.existsSync(p) };
27047
27657
  }
27048
27658
  const candidates = candidateConfigPaths();
27049
- const existing = candidates.filter((c) => fs20.existsSync(c));
27659
+ const existing = candidates.filter((c) => fs21.existsSync(c));
27050
27660
  if (existing.length > 1) {
27051
27661
  throw new InstallStop(
27052
27662
  EXIT_REFUSED,
@@ -27063,22 +27673,22 @@ function resolveConfigPath(explicitPath) {
27063
27673
  function resolveSymlinkPolicy(configPath, explicitPath, home = os5.homedir()) {
27064
27674
  let st;
27065
27675
  try {
27066
- st = fs20.lstatSync(configPath);
27676
+ st = fs21.lstatSync(configPath);
27067
27677
  } catch {
27068
27678
  return configPath;
27069
27679
  }
27070
27680
  if (!st.isSymbolicLink()) return configPath;
27071
27681
  let real;
27072
27682
  try {
27073
- real = fs20.realpathSync(configPath);
27683
+ real = fs21.realpathSync(configPath);
27074
27684
  } catch {
27075
27685
  throw new InstallStop(
27076
27686
  EXIT_REFUSED,
27077
27687
  `${configPath} is a link that points to a file that does not exist. Fix or remove the link, or run again with --path to a real file. Nothing was changed.`
27078
27688
  );
27079
27689
  }
27080
- const rel = path19.relative(path19.resolve(home), real);
27081
- const outsideHome = rel.startsWith("..") || path19.isAbsolute(rel);
27690
+ const rel = path20.relative(path20.resolve(home), real);
27691
+ const outsideHome = rel.startsWith("..") || path20.isAbsolute(rel);
27082
27692
  if (outsideHome && !explicitPath) {
27083
27693
  throw new InstallStop(
27084
27694
  EXIT_REFUSED,
@@ -27088,8 +27698,8 @@ function resolveSymlinkPolicy(configPath, explicitPath, home = os5.homedir()) {
27088
27698
  return real;
27089
27699
  }
27090
27700
  function readConfigBytes(configPath) {
27091
- const bytes = fs20.readFileSync(configPath);
27092
- const stat = fs20.statSync(configPath);
27701
+ const bytes = fs21.readFileSync(configPath);
27702
+ const stat = fs21.statSync(configPath);
27093
27703
  if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 254 && bytes[1] === 255)) {
27094
27704
  throw new InstallStop(
27095
27705
  EXIT_REFUSED,
@@ -27163,23 +27773,23 @@ function backupStamp(now) {
27163
27773
  }
27164
27774
  function chooseBackupPath(configPath, now = /* @__PURE__ */ new Date()) {
27165
27775
  const base = `${configPath}.bak-${backupStamp(now)}`;
27166
- if (!fs20.existsSync(base)) return base;
27776
+ if (!fs21.existsSync(base)) return base;
27167
27777
  for (let i = 2; ; i++) {
27168
27778
  const candidate = `${base}-${i}`;
27169
- if (!fs20.existsSync(candidate)) return candidate;
27779
+ if (!fs21.existsSync(candidate)) return candidate;
27170
27780
  }
27171
27781
  }
27172
27782
  function writeVerifiedBackup(configPath, originalBytes) {
27173
27783
  const backupPath = chooseBackupPath(configPath);
27174
27784
  try {
27175
- fs20.writeFileSync(backupPath, originalBytes);
27176
- const readBack = fs20.readFileSync(backupPath);
27785
+ fs21.writeFileSync(backupPath, originalBytes);
27786
+ const readBack = fs21.readFileSync(backupPath);
27177
27787
  if (!readBack.equals(originalBytes)) {
27178
27788
  throw new Error("backup read-back did not match");
27179
27789
  }
27180
27790
  } catch (e) {
27181
27791
  try {
27182
- fs20.rmSync(backupPath, { force: true });
27792
+ fs21.rmSync(backupPath, { force: true });
27183
27793
  } catch {
27184
27794
  }
27185
27795
  throw new InstallStop(
@@ -27198,9 +27808,9 @@ function baselineOf(bytes, stat) {
27198
27808
  function assertNotStale(configPath, baseline) {
27199
27809
  let ok = false;
27200
27810
  try {
27201
- const st = fs20.statSync(configPath);
27811
+ const st = fs21.statSync(configPath);
27202
27812
  if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
27203
- ok = sha2562(fs20.readFileSync(configPath)) === baseline.hash;
27813
+ ok = sha2562(fs21.readFileSync(configPath)) === baseline.hash;
27204
27814
  } else {
27205
27815
  ok = false;
27206
27816
  }
@@ -27215,14 +27825,14 @@ function assertNotStale(configPath, baseline) {
27215
27825
  }
27216
27826
  }
27217
27827
  async function atomicReplace(opts) {
27218
- const dir = path19.dirname(opts.configPath);
27219
- const tempPath = path19.join(dir, `.${path19.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`);
27220
- const fd = fs20.openSync(tempPath, "w");
27828
+ const dir = path20.dirname(opts.configPath);
27829
+ const tempPath = path20.join(dir, `.${path20.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`);
27830
+ const fd = fs21.openSync(tempPath, "w");
27221
27831
  try {
27222
- fs20.writeFileSync(fd, opts.newBytes);
27223
- fs20.fsyncSync(fd);
27832
+ fs21.writeFileSync(fd, opts.newBytes);
27833
+ fs21.fsyncSync(fd);
27224
27834
  } finally {
27225
- fs20.closeSync(fd);
27835
+ fs21.closeSync(fd);
27226
27836
  }
27227
27837
  try {
27228
27838
  for (let attempt = 0; ; attempt++) {
@@ -27247,17 +27857,17 @@ async function atomicReplace(opts) {
27247
27857
  }
27248
27858
  } catch (e) {
27249
27859
  try {
27250
- fs20.rmSync(tempPath, { force: true });
27860
+ fs21.rmSync(tempPath, { force: true });
27251
27861
  } catch {
27252
27862
  }
27253
27863
  throw e;
27254
27864
  }
27255
27865
  try {
27256
- const dirFd = fs20.openSync(dir, "r");
27866
+ const dirFd = fs21.openSync(dir, "r");
27257
27867
  try {
27258
- fs20.fsyncSync(dirFd);
27868
+ fs21.fsyncSync(dirFd);
27259
27869
  } finally {
27260
- fs20.closeSync(dirFd);
27870
+ fs21.closeSync(dirFd);
27261
27871
  }
27262
27872
  } catch {
27263
27873
  }
@@ -27309,7 +27919,7 @@ async function runInstall(argv, ioOverride) {
27309
27919
  try {
27310
27920
  const resolved2 = resolveConfigPath(flags.path);
27311
27921
  const configPath = resolveSymlinkPolicy(resolved2.configPath, flags.path !== void 0);
27312
- const exists = fs20.existsSync(configPath);
27922
+ const exists = fs21.existsSync(configPath);
27313
27923
  let read = null;
27314
27924
  let outcome;
27315
27925
  if (exists) {
@@ -27339,7 +27949,7 @@ async function runInstall(argv, ioOverride) {
27339
27949
  return EXIT_OK;
27340
27950
  }
27341
27951
  const backupPath = exists && read ? writeVerifiedBackup(configPath, read.bytes) : null;
27342
- if (!exists) fs20.mkdirSync(path19.dirname(configPath), { recursive: true });
27952
+ if (!exists) fs21.mkdirSync(path20.dirname(configPath), { recursive: true });
27343
27953
  await atomicReplace({
27344
27954
  configPath,
27345
27955
  newBytes,
@@ -27357,13 +27967,13 @@ async function runInstall(argv, ioOverride) {
27357
27967
  return EXIT_ABORTED;
27358
27968
  }
27359
27969
  }
27360
- var fs20, os5, path19, import_node_crypto5, import_node_util, import_json5, SERVER_KEY, DESIRED_ENTRY, EXIT_OK, EXIT_USAGE, EXIT_REFUSED, EXIT_ABORTED, RENAME_RETRY_DELAYS_MS, LOCK_ERROR_CODES, BOM_UTF8, InstallStop;
27970
+ var fs21, os5, path20, import_node_crypto5, import_node_util, import_json5, SERVER_KEY, DESIRED_ENTRY, EXIT_OK, EXIT_USAGE, EXIT_REFUSED, EXIT_ABORTED, RENAME_RETRY_DELAYS_MS, LOCK_ERROR_CODES, BOM_UTF8, InstallStop;
27361
27971
  var init_config_installer = __esm({
27362
27972
  "src/config-installer.ts"() {
27363
27973
  "use strict";
27364
- fs20 = __toESM(require("node:fs"));
27974
+ fs21 = __toESM(require("node:fs"));
27365
27975
  os5 = __toESM(require("node:os"));
27366
- path19 = __toESM(require("node:path"));
27976
+ path20 = __toESM(require("node:path"));
27367
27977
  import_node_crypto5 = require("node:crypto");
27368
27978
  import_node_util = require("node:util");
27369
27979
  import_json5 = __toESM(require("json5"));
@@ -27394,7 +28004,7 @@ var require_package = __commonJS({
27394
28004
  "package.json"(exports2, module2) {
27395
28005
  module2.exports = {
27396
28006
  name: "@elitedcs/ghl-mcp",
27397
- version: "3.73.0",
28007
+ version: "3.74.0",
27398
28008
  mcpName: "io.github.drjerryrelth/ghl-command",
27399
28009
  description: "GoHighLevel MCP Server for Claude. 247 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.",
27400
28010
  main: "dist/index.js",
@@ -27492,7 +28102,7 @@ function launchCommand(port = 7300) {
27492
28102
  return `npx -y @elitedcs/ghl-mcp@latest dashboard --port=${port}`;
27493
28103
  }
27494
28104
  function installingEntry() {
27495
- return path21.join(__dirname, "index.js");
28105
+ return path22.join(__dirname, "index.js");
27496
28106
  }
27497
28107
  function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
27498
28108
  const cmd = launchCommand(port);
@@ -27500,12 +28110,12 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
27500
28110
  const systemApps = "/Applications";
27501
28111
  let base = home;
27502
28112
  try {
27503
- fs22.accessSync(systemApps, fs22.constants.W_OK);
28113
+ fs23.accessSync(systemApps, fs23.constants.W_OK);
27504
28114
  base = "";
27505
28115
  } catch {
27506
28116
  }
27507
- const appDir = base ? path21.join(home, "Applications", "Command OS.app") : path21.join(systemApps, "Command OS.app");
27508
- const macOSDir = path21.join(appDir, "Contents", "MacOS");
28117
+ const appDir = base ? path22.join(home, "Applications", "Command OS.app") : path22.join(systemApps, "Command OS.app");
28118
+ const macOSDir = path22.join(appDir, "Contents", "MacOS");
27509
28119
  const script = [
27510
28120
  "#!/bin/bash",
27511
28121
  "# GHL Command \u2014 Command OS launcher (regenerate: ghl-mcp install-launcher)",
@@ -27534,13 +28144,13 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
27534
28144
  targetPath: appDir,
27535
28145
  humanLocation: base ? "your personal Applications folder at ~/Applications (Finder \u2192 Go \u2192 Home \u2192 Applications)" : "your Applications folder \u2014 open Finder \u2192 Applications and look for \u201CCommand OS\u201D",
27536
28146
  files: [
27537
- { path: path21.join(macOSDir, "command-os"), contents: script, executable: true },
27538
- { path: path21.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
28147
+ { path: path22.join(macOSDir, "command-os"), contents: script, executable: true },
28148
+ { path: path22.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
27539
28149
  ]
27540
28150
  };
27541
28151
  }
27542
28152
  if (platform2 === "win32") {
27543
- const target2 = path21.join(home, "Desktop", "Command OS.cmd");
28153
+ const target2 = path22.join(home, "Desktop", "Command OS.cmd");
27544
28154
  return {
27545
28155
  platform: platform2,
27546
28156
  targetPath: target2,
@@ -27553,7 +28163,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
27553
28163
  ].join("\r\n"), executable: false }]
27554
28164
  };
27555
28165
  }
27556
- const target = path21.join(home, ".local", "share", "applications", "command-os.desktop");
28166
+ const target = path22.join(home, ".local", "share", "applications", "command-os.desktop");
27557
28167
  return {
27558
28168
  platform: platform2,
27559
28169
  targetPath: target,
@@ -27580,8 +28190,8 @@ function installLauncher(argv = []) {
27580
28190
  const plan = planLauncher(process.platform, os6.homedir(), port, installingEntry());
27581
28191
  try {
27582
28192
  for (const f of plan.files) {
27583
- fs22.mkdirSync(path21.dirname(f.path), { recursive: true });
27584
- fs22.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
28193
+ fs23.mkdirSync(path22.dirname(f.path), { recursive: true });
28194
+ fs23.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
27585
28195
  }
27586
28196
  } catch (e) {
27587
28197
  process.stderr.write(`
@@ -27601,13 +28211,13 @@ function installLauncher(argv = []) {
27601
28211
  ].join("\n"));
27602
28212
  return 0;
27603
28213
  }
27604
- var fs22, os6, path21;
28214
+ var fs23, os6, path22;
27605
28215
  var init_launcher = __esm({
27606
28216
  "src/launcher.ts"() {
27607
28217
  "use strict";
27608
- fs22 = __toESM(require("fs"));
28218
+ fs23 = __toESM(require("fs"));
27609
28219
  os6 = __toESM(require("os"));
27610
- path21 = __toESM(require("path"));
28220
+ path22 = __toESM(require("path"));
27611
28221
  }
27612
28222
  });
27613
28223
 
@@ -28514,11 +29124,11 @@ var init_client_status = __esm({
28514
29124
 
28515
29125
  // src/command-os/client-status-placement.ts
28516
29126
  function statusStorePath(base = appDataDir()) {
28517
- return path22.join(base, "client-status.json");
29127
+ return path23.join(base, "client-status.json");
28518
29128
  }
28519
29129
  function readStatusStore(base) {
28520
29130
  try {
28521
- const raw = JSON.parse(fs23.readFileSync(statusStorePath(base), "utf8"));
29131
+ const raw = JSON.parse(fs24.readFileSync(statusStorePath(base), "utf8"));
28522
29132
  if (raw && raw.v === 1 && raw.clients) return raw;
28523
29133
  } catch {
28524
29134
  }
@@ -28526,8 +29136,8 @@ function readStatusStore(base) {
28526
29136
  }
28527
29137
  function writeStatusStore(store, base) {
28528
29138
  const file = statusStorePath(base);
28529
- fs23.mkdirSync(path22.dirname(file), { recursive: true, mode: 448 });
28530
- fs23.writeFileSync(file, JSON.stringify(store, null, 2), { mode: 384 });
29139
+ fs24.mkdirSync(path23.dirname(file), { recursive: true, mode: 448 });
29140
+ fs24.writeFileSync(file, JSON.stringify(store, null, 2), { mode: 384 });
28531
29141
  }
28532
29142
  function readStatusRecord(locationId2, base) {
28533
29143
  return readStatusStore(base).clients[locationId2] ?? { locationId: locationId2 };
@@ -28559,9 +29169,9 @@ function describePlacement(placement) {
28559
29169
  return `A real page inside the client's own GoHighLevel account, at /${placement.slug ?? STATUS_SLUG}${placement.publicUrl ? ` \u2014 ${placement.publicUrl}` : ""}. Every re-check replaces that one page; it never adds a second copy.`;
28560
29170
  }
28561
29171
  if (placement.kind === "agency-site") {
28562
- return `A file on your own site: ${path22.join(placement.directory, placement.fileName ?? "status.html")}${placement.publicUrl ? `, which you publish at ${placement.publicUrl}` : ""}. The same file is rewritten every time, so the address never changes.`;
29172
+ return `A file on your own site: ${path23.join(placement.directory, placement.fileName ?? "status.html")}${placement.publicUrl ? `, which you publish at ${placement.publicUrl}` : ""}. The same file is rewritten every time, so the address never changes.`;
28563
29173
  }
28564
- return `A page written to ${path22.join(placement.directory, placement.fileName ?? "status.html")} for you to publish as an Artifact from your own Claude${placement.publicUrl ? `, currently at ${placement.publicUrl}` : ""}.`;
29174
+ return `A page written to ${path23.join(placement.directory, placement.fileName ?? "status.html")} for you to publish as an Artifact from your own Claude${placement.publicUrl ? `, currently at ${placement.publicUrl}` : ""}.`;
28565
29175
  }
28566
29176
  function placementReadiness(placement) {
28567
29177
  if (!placement) return ["Choose where this client's page should live: their own GoHighLevel account, your own site, or an Artifact from your own Claude."];
@@ -28620,16 +29230,25 @@ function artifactPublishSteps(filePath, existingUrl) {
28620
29230
  "Send the client the link once. From then on they just revisit it."
28621
29231
  ];
28622
29232
  }
28623
- var fs23, path22, STATUS_SLUG, OUR_HOSTS;
29233
+ var fs24, path23, STATUS_SLUG, OUR_HOSTS;
28624
29234
  var init_client_status_placement = __esm({
28625
29235
  "src/command-os/client-status-placement.ts"() {
28626
29236
  "use strict";
28627
- fs23 = __toESM(require("node:fs"));
28628
- path22 = __toESM(require("node:path"));
29237
+ fs24 = __toESM(require("node:fs"));
29238
+ path23 = __toESM(require("node:path"));
28629
29239
  init_credentials_store();
28630
29240
  init_client_status();
28631
29241
  STATUS_SLUG = "status";
28632
- OUR_HOSTS = ["elitedcs.com", "ghlcommand.com", "commandos-hub.pages.dev"];
29242
+ OUR_HOSTS = [
29243
+ "elitedcs.com",
29244
+ "ghlcommand.com",
29245
+ "commandos-hub.pages.dev",
29246
+ // The member hub's real address. It has to be in this list for the same
29247
+ // reason the others are: a client's status page here would mean we are
29248
+ // holding a customer's client data.
29249
+ "os.ghlcommand.com",
29250
+ "command-os.pages.dev"
29251
+ ];
28633
29252
  }
28634
29253
  });
28635
29254
 
@@ -28743,24 +29362,24 @@ var init_client_status_page = __esm({
28743
29362
 
28744
29363
  // src/command-os/client-status-publish.ts
28745
29364
  function handoffSnapshotDir(base = appDataDir()) {
28746
- return path23.join(base, "handoffs");
29365
+ return path24.join(base, "handoffs");
28747
29366
  }
28748
29367
  function handoffSnapshotPath(locationId2, base) {
28749
- return path23.join(handoffSnapshotDir(base), `${locationId2}-handoff.json`);
29368
+ return path24.join(handoffSnapshotDir(base), `${locationId2}-handoff.json`);
28750
29369
  }
28751
29370
  function readHandoffSnapshot(locationId2, base) {
28752
29371
  try {
28753
- return JSON.parse(fs24.readFileSync(handoffSnapshotPath(locationId2, base), "utf8"));
29372
+ return JSON.parse(fs25.readFileSync(handoffSnapshotPath(locationId2, base), "utf8"));
28754
29373
  } catch {
28755
29374
  return null;
28756
29375
  }
28757
29376
  }
28758
29377
  function localCopyPath(locationId2, base = appDataDir()) {
28759
- return path23.join(base, "client-status", `${locationId2}-status.html`);
29378
+ return path24.join(base, "client-status", `${locationId2}-status.html`);
28760
29379
  }
28761
29380
  function readVerifyOutcome(locationId2, base = appDataDir()) {
28762
29381
  try {
28763
- const st = JSON.parse(fs24.readFileSync(path23.join(base, "cockpit-state.json"), "utf8"));
29382
+ const st = JSON.parse(fs25.readFileSync(path24.join(base, "cockpit-state.json"), "utf8"));
28764
29383
  const o = st.outcomes?.[`${locationId2}:4`];
28765
29384
  if (!o) return null;
28766
29385
  const src = o.plain ?? { summary: o.summary, issues: o.issues };
@@ -28770,9 +29389,9 @@ function readVerifyOutcome(locationId2, base = appDataDir()) {
28770
29389
  }
28771
29390
  }
28772
29391
  function writeFileFor(dir, fileName, html2) {
28773
- fs24.mkdirSync(dir, { recursive: true });
28774
- const file = path23.join(dir, fileName);
28775
- fs24.writeFileSync(file, html2);
29392
+ fs25.mkdirSync(dir, { recursive: true });
29393
+ const file = path24.join(dir, fileName);
29394
+ fs25.writeFileSync(file, html2);
28776
29395
  return file;
28777
29396
  }
28778
29397
  async function refreshClientStatus(opts) {
@@ -28804,7 +29423,7 @@ async function refreshClientStatus(opts) {
28804
29423
  );
28805
29424
  const html2 = renderClientStatusHtml(input);
28806
29425
  const localPath = localCopyPath(opts.locationId, opts.base);
28807
- if (!opts.dryRun) writeFileFor(path23.dirname(localPath), path23.basename(localPath), html2);
29426
+ if (!opts.dryRun) writeFileFor(path24.dirname(localPath), path24.basename(localPath), html2);
28808
29427
  const placement = record.placement;
28809
29428
  const missing = placementReadiness(placement);
28810
29429
  if (!placement || missing.length) {
@@ -28849,7 +29468,7 @@ async function publishTo(placement, input, html2, opts) {
28849
29468
  if (placement.kind === "agency-site") {
28850
29469
  assertNotOurHost(placement.publicUrl, "The address you chose for this page");
28851
29470
  const fileName = placement.fileName ?? "status.html";
28852
- const destinationPath = opts.dryRun ? path23.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
29471
+ const destinationPath = opts.dryRun ? path24.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
28853
29472
  return {
28854
29473
  ...placement.publicUrl && { url: placement.publicUrl },
28855
29474
  destinationPath,
@@ -28863,7 +29482,7 @@ async function publishTo(placement, input, html2, opts) {
28863
29482
  const safety = artifactSafety(html2);
28864
29483
  if (!safety.ok) throw new Error(safety.problems.join(" "));
28865
29484
  const fileName = placement.fileName ?? "status.html";
28866
- const destinationPath = opts.dryRun ? path23.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
29485
+ const destinationPath = opts.dryRun ? path24.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
28867
29486
  assertNotOurHost(placement.publicUrl, "The Artifact link saved for this client");
28868
29487
  return {
28869
29488
  ...placement.publicUrl && { url: placement.publicUrl },
@@ -28902,12 +29521,12 @@ async function refreshAfterVerify(locationId2, accountName, emit2, base) {
28902
29521
  emit2(`The client's status page was not updated: ${e instanceof Error ? e.message : String(e)}`);
28903
29522
  }
28904
29523
  }
28905
- var fs24, path23;
29524
+ var fs25, path24;
28906
29525
  var init_client_status_publish = __esm({
28907
29526
  "src/command-os/client-status-publish.ts"() {
28908
29527
  "use strict";
28909
- fs24 = __toESM(require("node:fs"));
28910
- path23 = __toESM(require("node:path"));
29528
+ fs25 = __toESM(require("node:fs"));
29529
+ path24 = __toESM(require("node:path"));
28911
29530
  init_credentials_store();
28912
29531
  init_branding();
28913
29532
  init_client_status();
@@ -29222,11 +29841,11 @@ function recoverStuckStages(state) {
29222
29841
  return { state: { ...state, clients }, recovered };
29223
29842
  }
29224
29843
  function cockpitStatePath() {
29225
- return path24.join(appDataDir(), "cockpit-state.json");
29844
+ return path25.join(appDataDir(), "cockpit-state.json");
29226
29845
  }
29227
29846
  function readCockpitState() {
29228
29847
  try {
29229
- const raw = JSON.parse(fs25.readFileSync(cockpitStatePath(), "utf8"));
29848
+ const raw = JSON.parse(fs26.readFileSync(cockpitStatePath(), "utf8"));
29230
29849
  if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
29231
29850
  } catch {
29232
29851
  }
@@ -29234,7 +29853,7 @@ function readCockpitState() {
29234
29853
  }
29235
29854
  function writeCockpitState(state) {
29236
29855
  ensureAppDataDir();
29237
- fs25.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
29856
+ fs26.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
29238
29857
  }
29239
29858
  function setStage(state, locationId2, stageIndex, status) {
29240
29859
  if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES.length) throw new Error("bad stage index");
@@ -29376,7 +29995,7 @@ function notify(message) {
29376
29995
  `);
29377
29996
  try {
29378
29997
  ensureAppDataDir();
29379
- fs25.appendFileSync(path24.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
29998
+ fs26.appendFileSync(path25.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
29380
29999
  `);
29381
30000
  } catch {
29382
30001
  }
@@ -29413,7 +30032,7 @@ function renderCockpitHtml(roster, seatToken = "") {
29413
30032
  return html(roster, seatToken);
29414
30033
  }
29415
30034
  function html(roster, seatToken) {
29416
- const HUB_URL = "https://commandos-hub.pages.dev/";
30035
+ const HUB_URL = "https://os.ghlcommand.com/";
29417
30036
  return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Command OS \u2014 GHL Command</title>
29418
30037
  <link rel="preconnect" href="https://fonts.googleapis.com">
29419
30038
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -31027,12 +31646,12 @@ async function runDashboard(argv) {
31027
31646
  process.on("SIGTERM", stop);
31028
31647
  });
31029
31648
  }
31030
- var fs25, path24, http, import_child_process4, import_child_process5, osmod, STAGES, ACCOUNT_TYPES, activeRun, UPGRADE_MSG, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG;
31649
+ var fs26, path25, http, import_child_process4, import_child_process5, osmod, STAGES, ACCOUNT_TYPES, activeRun, UPGRADE_MSG, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG;
31031
31650
  var init_dashboard = __esm({
31032
31651
  "src/dashboard.ts"() {
31033
31652
  "use strict";
31034
- fs25 = __toESM(require("fs"));
31035
- path24 = __toESM(require("path"));
31653
+ fs26 = __toESM(require("fs"));
31654
+ path25 = __toESM(require("path"));
31036
31655
  http = __toESM(require("http"));
31037
31656
  import_child_process4 = require("child_process");
31038
31657
  init_credentials_store();
@@ -31079,8 +31698,8 @@ var init_dashboard = __esm({
31079
31698
 
31080
31699
  // src/index.ts
31081
31700
  var dotenv2 = __toESM(require("dotenv"));
31082
- var path25 = __toESM(require("path"));
31083
- var fs26 = __toESM(require("fs"));
31701
+ var path26 = __toESM(require("path"));
31702
+ var fs27 = __toESM(require("fs"));
31084
31703
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
31085
31704
 
31086
31705
  // src/version-compare.ts
@@ -31148,20 +31767,20 @@ init_credentials_store();
31148
31767
  init_setup_tool();
31149
31768
 
31150
31769
  // src/tools/skills.ts
31151
- var import_zod67 = require("zod");
31770
+ var import_zod68 = require("zod");
31152
31771
 
31153
31772
  // src/skill-installer.ts
31154
31773
  var import_node_crypto4 = require("node:crypto");
31155
- var fs19 = __toESM(require("node:fs"));
31156
- var path18 = __toESM(require("node:path"));
31774
+ var fs20 = __toESM(require("node:fs"));
31775
+ var path19 = __toESM(require("node:path"));
31157
31776
  var os4 = __toESM(require("node:os"));
31158
31777
  function sha256(buf) {
31159
31778
  return (0, import_node_crypto4.createHash)("sha256").update(buf).digest("hex");
31160
31779
  }
31161
31780
  function bundledSkillsDir(baseDir) {
31162
- const candidate = path18.resolve(baseDir, "..", "skills");
31781
+ const candidate = path19.resolve(baseDir, "..", "skills");
31163
31782
  try {
31164
- return fs19.statSync(candidate).isDirectory() ? candidate : null;
31783
+ return fs20.statSync(candidate).isDirectory() ? candidate : null;
31165
31784
  } catch {
31166
31785
  return null;
31167
31786
  }
@@ -31169,10 +31788,10 @@ function bundledSkillsDir(baseDir) {
31169
31788
  function listFiles(root) {
31170
31789
  const out = [];
31171
31790
  const walk = (dir) => {
31172
- for (const entry of fs19.readdirSync(dir, { withFileTypes: true })) {
31173
- const full = path18.join(dir, entry.name);
31791
+ for (const entry of fs20.readdirSync(dir, { withFileTypes: true })) {
31792
+ const full = path19.join(dir, entry.name);
31174
31793
  if (entry.isDirectory()) walk(full);
31175
- else if (entry.isFile()) out.push(path18.relative(root, full));
31794
+ else if (entry.isFile()) out.push(path19.relative(root, full));
31176
31795
  }
31177
31796
  };
31178
31797
  walk(root);
@@ -31180,7 +31799,7 @@ function listFiles(root) {
31180
31799
  }
31181
31800
  function readMarker(markerPath) {
31182
31801
  try {
31183
- const parsed = JSON.parse(fs19.readFileSync(markerPath, "utf8"));
31802
+ const parsed = JSON.parse(fs20.readFileSync(markerPath, "utf8"));
31184
31803
  return parsed && typeof parsed === "object" && parsed.files ? parsed : null;
31185
31804
  } catch {
31186
31805
  return null;
@@ -31193,35 +31812,35 @@ function installBundledSkills(opts) {
31193
31812
  skippedUserModified: [],
31194
31813
  unchanged: [],
31195
31814
  errors: [],
31196
- targetDir: opts.targetDir ?? path18.join(os4.homedir(), ".claude", "skills"),
31815
+ targetDir: opts.targetDir ?? path19.join(os4.homedir(), ".claude", "skills"),
31197
31816
  bundledDir: null
31198
31817
  };
31199
31818
  const bundled = bundledSkillsDir(opts.baseDir);
31200
31819
  result.bundledDir = bundled;
31201
31820
  if (!bundled) return result;
31202
31821
  try {
31203
- fs19.mkdirSync(result.targetDir, { recursive: true });
31822
+ fs20.mkdirSync(result.targetDir, { recursive: true });
31204
31823
  } catch (e) {
31205
31824
  result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
31206
31825
  return result;
31207
31826
  }
31208
- const markerPath = path18.join(result.targetDir, ".ghl-command-skills.json");
31827
+ const markerPath = path19.join(result.targetDir, ".ghl-command-skills.json");
31209
31828
  const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
31210
31829
  const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
31211
31830
  for (const rel of listFiles(bundled)) {
31212
31831
  try {
31213
- const src = fs19.readFileSync(path18.join(bundled, rel));
31214
- const dest = path18.join(result.targetDir, rel);
31832
+ const src = fs20.readFileSync(path19.join(bundled, rel));
31833
+ const dest = path19.join(result.targetDir, rel);
31215
31834
  const srcHash = sha256(src);
31216
31835
  let destBuf = null;
31217
31836
  try {
31218
- destBuf = fs19.readFileSync(dest);
31837
+ destBuf = fs20.readFileSync(dest);
31219
31838
  } catch {
31220
31839
  destBuf = null;
31221
31840
  }
31222
31841
  if (destBuf === null) {
31223
- fs19.mkdirSync(path18.dirname(dest), { recursive: true });
31224
- fs19.writeFileSync(dest, src);
31842
+ fs20.mkdirSync(path19.dirname(dest), { recursive: true });
31843
+ fs20.writeFileSync(dest, src);
31225
31844
  newMarker.files[rel] = srcHash;
31226
31845
  result.installed.push(rel);
31227
31846
  continue;
@@ -31234,7 +31853,7 @@ function installBundledSkills(opts) {
31234
31853
  }
31235
31854
  const lastWritten = marker.files[rel];
31236
31855
  if (lastWritten && destHash === lastWritten) {
31237
- fs19.writeFileSync(dest, src);
31856
+ fs20.writeFileSync(dest, src);
31238
31857
  newMarker.files[rel] = srcHash;
31239
31858
  result.updated.push(rel);
31240
31859
  } else {
@@ -31245,7 +31864,7 @@ function installBundledSkills(opts) {
31245
31864
  }
31246
31865
  }
31247
31866
  try {
31248
- fs19.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
31867
+ fs20.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
31249
31868
  } catch (e) {
31250
31869
  result.errors.push(`marker write failed: ${String(e)}`);
31251
31870
  }
@@ -31269,7 +31888,7 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
31269
31888
  "install_skills",
31270
31889
  "Install (or repair) the guided skills bundled with GHL Command \u2014 Blueprint (build a whole client account from one intake), Clone Site (clone and rebrand a live web page for a client, with a rights declaration and a pre-launch liability report), and GHL Reports (verified counts, lists, and weekly reports without token burn) \u2014 into your ~/.claude/skills/ so Claude can use them. Runs automatically on startup; call this to verify what is installed, or to re-install after deleting a skill. NEVER overwrites files you have edited (your version is kept and reported). Restart Claude after install for new skills to load.",
31271
31890
  {
31272
- targetDir: import_zod67.z.string().optional().describe("Override the install directory. Default: ~/.claude/skills")
31891
+ targetDir: import_zod68.z.string().optional().describe("Override the install directory. Default: ~/.claude/skills")
31273
31892
  },
31274
31893
  async ({ targetDir }) => {
31275
31894
  try {
@@ -31380,8 +31999,8 @@ init_version_check();
31380
31999
 
31381
32000
  // src/cli.ts
31382
32001
  var import_node_util2 = require("node:util");
31383
- var fs21 = __toESM(require("fs"));
31384
- var path20 = __toESM(require("path"));
32002
+ var fs22 = __toESM(require("fs"));
32003
+ var path21 = __toESM(require("path"));
31385
32004
  var import_crypto2 = require("crypto");
31386
32005
  init_ghl_client();
31387
32006
  init_token_registry();
@@ -31432,9 +32051,9 @@ function errLine(msg3) {
31432
32051
  function preflightWritable() {
31433
32052
  try {
31434
32053
  const dir = ensureAppDataDir();
31435
- const probe = path20.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
31436
- fs21.writeFileSync(probe, "ok");
31437
- fs21.unlinkSync(probe);
32054
+ const probe = path21.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
32055
+ fs22.writeFileSync(probe, "ok");
32056
+ fs22.unlinkSync(probe);
31438
32057
  return true;
31439
32058
  } catch (error) {
31440
32059
  errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
@@ -31688,7 +32307,7 @@ var bundledPkg = require_package();
31688
32307
  var pkg = (() => {
31689
32308
  try {
31690
32309
  const onDisk = JSON.parse(
31691
- fs26.readFileSync(path25.resolve(__dirname, "..", "package.json"), "utf8")
32310
+ fs27.readFileSync(path26.resolve(__dirname, "..", "package.json"), "utf8")
31692
32311
  );
31693
32312
  if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
31694
32313
  return { version: onDisk.version };
@@ -31701,7 +32320,7 @@ dotenv2.config();
31701
32320
  setPkgVersion(pkg.version);
31702
32321
  {
31703
32322
  const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
31704
- if (configDirOverride && !path25.isAbsolute(configDirOverride)) {
32323
+ if (configDirOverride && !path26.isAbsolute(configDirOverride)) {
31705
32324
  process.stderr.write(
31706
32325
  `[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.
31707
32326
  `
@@ -31714,20 +32333,20 @@ process.on("unhandledRejection", (reason) => {
31714
32333
  `);
31715
32334
  });
31716
32335
  function hardenSecretFilePerms() {
31717
- const repoDir = path25.resolve(__dirname, "..");
32336
+ const repoDir = path26.resolve(__dirname, "..");
31718
32337
  const candidates = [
31719
- { file: path25.join(repoDir, "start-mcp.sh"), mode: 448 },
32338
+ { file: path26.join(repoDir, "start-mcp.sh"), mode: 448 },
31720
32339
  // Legacy registry location (pre-migration); new location lives in app-data.
31721
- { file: path25.join(repoDir, ".ghl-tokens.json"), mode: 384 },
32340
+ { file: path26.join(repoDir, ".ghl-tokens.json"), mode: 384 },
31722
32341
  { file: tokenRegistryPath(), mode: 384 }
31723
32342
  ];
31724
32343
  for (const { file, mode } of candidates) {
31725
32344
  let current;
31726
32345
  try {
31727
- if (!fs26.existsSync(file)) continue;
31728
- current = fs26.statSync(file).mode & 511;
32346
+ if (!fs27.existsSync(file)) continue;
32347
+ current = fs27.statSync(file).mode & 511;
31729
32348
  if (current !== mode) {
31730
- fs26.chmodSync(file, mode);
32349
+ fs27.chmodSync(file, mode);
31731
32350
  }
31732
32351
  } catch (error) {
31733
32352
  const message = error instanceof Error ? error.message : String(error);