@elitedcs/ghl-mcp 3.83.8 → 3.83.9

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
@@ -443,13 +443,13 @@ function setProtectedAccount(locationId2, name, file2 = accountRulesPath()) {
443
443
  function isWriteMethod(method) {
444
444
  return WRITE_METHODS.has(method.toUpperCase());
445
445
  }
446
- function locationIdsInRequest(path39, opts = {}) {
446
+ function locationIdsInRequest(path40, opts = {}) {
447
447
  const found = /* @__PURE__ */ new Set();
448
448
  const add = (v) => {
449
449
  if (typeof v === "string" && v.trim()) found.add(v.trim());
450
450
  else if (Array.isArray(v)) v.forEach(add);
451
451
  };
452
- const seg = /\/locations\/([^/?#]+)/i.exec(path39);
452
+ const seg = /\/locations\/([^/?#]+)/i.exec(path40);
453
453
  if (seg) add(decodeURIComponent(seg[1]));
454
454
  for (const src of [opts.params, opts.body]) {
455
455
  if (!src || typeof src !== "object") continue;
@@ -476,7 +476,7 @@ function protectedRefusalFor(locationId2, rulesFile) {
476
476
  const name = rules.protected[id];
477
477
  return name ? protectedRefusalMessage(name, false) : null;
478
478
  }
479
- function assertWriteAllowed(method, path39, opts = {}) {
479
+ function assertWriteAllowed(method, path40, opts = {}) {
480
480
  if (!isWriteMethod(method)) return;
481
481
  let rules;
482
482
  try {
@@ -485,7 +485,7 @@ function assertWriteAllowed(method, path39, opts = {}) {
485
485
  throw new Error(UNREADABLE_RULES_MSG);
486
486
  }
487
487
  if (Object.keys(rules.protected).length === 0) return;
488
- for (const id of locationIdsInRequest(path39, opts)) {
488
+ for (const id of locationIdsInRequest(path40, opts)) {
489
489
  const name = rules.protected[id];
490
490
  if (name) throw new Error(protectedRefusalMessage(name, false));
491
491
  }
@@ -552,6 +552,10 @@ function requestError(error) {
552
552
  function isGHLRequestError(error) {
553
553
  return error !== null && typeof error === "object" && requestErrors.has(error);
554
554
  }
555
+ function keyHint(key) {
556
+ if (!key) return "NOT SET";
557
+ return key.length > 8 ? `ending \u2026${key.slice(-4)}` : "set";
558
+ }
555
559
  var GHL_BASE_URL, GHL_API_VERSION, MAX_RETRIES, BASE_DELAY_MS, requestErrors, GHLClient;
556
560
  var init_ghl_client = __esm({
557
561
  "src/ghl-client.ts"() {
@@ -587,10 +591,10 @@ var init_ghl_client = __esm({
587
591
  this.apiKey = apiKey2;
588
592
  }
589
593
  /**
590
- * Get the current API key (for diagnostics)
594
+ * Which key is loaded, for diagnostics — never the key itself. See keyHint.
591
595
  */
592
- getApiKeyPrefix() {
593
- return this.apiKey ? `${this.apiKey.substring(0, 12)}...` : "NOT SET";
596
+ getApiKeyHint() {
597
+ return keyHint(this.apiKey);
594
598
  }
595
599
  /**
596
600
  * Return the current API key. Only used internally by switch_location to
@@ -607,8 +611,8 @@ var init_ghl_client = __esm({
607
611
  Version: version || GHL_API_VERSION
608
612
  };
609
613
  }
610
- buildUrl(path39, params) {
611
- const url = new URL(path39, GHL_BASE_URL);
614
+ buildUrl(path40, params) {
615
+ const url = new URL(path40, GHL_BASE_URL);
612
616
  if (params) {
613
617
  for (const [key, value] of Object.entries(params)) {
614
618
  if (value !== void 0 && value !== null) {
@@ -618,18 +622,18 @@ var init_ghl_client = __esm({
618
622
  }
619
623
  return url.toString();
620
624
  }
621
- async request(method, path39, options = {}, attempt = 0) {
625
+ async request(method, path40, options = {}, attempt = 0) {
622
626
  if (inBuildRequestScope() && method === "GET" && !inAccountReadAttempt()) {
623
- return retryAccountRead(() => this.request(method, path39, options, attempt), `${method} ${path39}`);
627
+ return retryAccountRead(() => this.request(method, path40, options, attempt), `${method} ${path40}`);
624
628
  }
625
629
  if (attempt === 0) {
626
- assertWriteAllowed(method, path39, {
630
+ assertWriteAllowed(method, path40, {
627
631
  params: options.params,
628
632
  body: options.body,
629
633
  defaultLocationId: this.defaultLocationId
630
634
  });
631
635
  }
632
- const url = this.buildUrl(path39, options.params);
636
+ const url = this.buildUrl(path40, options.params);
633
637
  const headers = this.buildHeaders(options.version);
634
638
  const fetchOptions = {
635
639
  method,
@@ -647,14 +651,14 @@ var init_ghl_client = __esm({
647
651
  } catch (error) {
648
652
  clearTimeout(timeout);
649
653
  if (error instanceof Error && error.name === "AbortError") {
650
- throw requestError(new Error(`Request timeout (30s): ${method} ${path39}`));
654
+ throw requestError(new Error(`Request timeout (30s): ${method} ${path40}`));
651
655
  }
652
656
  if (!inBuildRequestScope() && !options.noRetry && attempt < MAX_RETRIES) {
653
657
  const delay5 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
654
- process.stderr.write(`[ghl-mcp] Network error on ${method} ${path39}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
658
+ process.stderr.write(`[ghl-mcp] Network error on ${method} ${path40}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
655
659
  `);
656
660
  await new Promise((r) => setTimeout(r, delay5));
657
- return this.request(method, path39, options, attempt + 1);
661
+ return this.request(method, path40, options, attempt + 1);
658
662
  }
659
663
  throw requestError(error);
660
664
  } finally {
@@ -662,10 +666,10 @@ var init_ghl_client = __esm({
662
666
  }
663
667
  if (!inBuildRequestScope() && !options.noRetry && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES) {
664
668
  const delay5 = computeRetryDelay(response.headers.get("Retry-After"), attempt, BASE_DELAY_MS);
665
- process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path39}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
669
+ process.stderr.write(`[ghl-mcp] ${response.status} on ${method} ${path40}, retry ${attempt + 1}/${MAX_RETRIES} in ${delay5}ms
666
670
  `);
667
671
  await new Promise((r) => setTimeout(r, delay5));
668
- return this.request(method, path39, options, attempt + 1);
672
+ return this.request(method, path40, options, attempt + 1);
669
673
  }
670
674
  if (!response.ok) {
671
675
  let errorBody = "";
@@ -674,7 +678,7 @@ var init_ghl_client = __esm({
674
678
  } catch {
675
679
  }
676
680
  throw requestError(new Error(
677
- `GHL API Error ${response.status} ${response.statusText}: ${method} ${path39}
681
+ `GHL API Error ${response.status} ${response.statusText}: ${method} ${path40}
678
682
  ${errorBody}`
679
683
  ));
680
684
  }
@@ -691,20 +695,20 @@ ${errorBody}`
691
695
  return { message: text };
692
696
  }
693
697
  }
694
- async get(path39, options) {
695
- return this.request("GET", path39, options);
698
+ async get(path40, options) {
699
+ return this.request("GET", path40, options);
696
700
  }
697
- async post(path39, options) {
698
- return this.request("POST", path39, options);
701
+ async post(path40, options) {
702
+ return this.request("POST", path40, options);
699
703
  }
700
- async put(path39, options) {
701
- return this.request("PUT", path39, options);
704
+ async put(path40, options) {
705
+ return this.request("PUT", path40, options);
702
706
  }
703
- async patch(path39, options) {
704
- return this.request("PATCH", path39, options);
707
+ async patch(path40, options) {
708
+ return this.request("PATCH", path40, options);
705
709
  }
706
- async delete(path39, options) {
707
- return this.request("DELETE", path39, options);
710
+ async delete(path40, options) {
711
+ return this.request("DELETE", path40, options);
708
712
  }
709
713
  /**
710
714
  * Helper: resolves locationId from args or falls back to default
@@ -7467,11 +7471,11 @@ function registerEmailTools(server2, client) {
7467
7471
  function registerEmailBuilderInternalTools(server2, builderClient, publicClient) {
7468
7472
  if (!builderClient) return;
7469
7473
  const client = builderClient;
7470
- async function builderRequest2(method, path39, body) {
7471
- const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path39}`, body ? { body } : {});
7474
+ async function builderRequest2(method, path40, body) {
7475
+ const response = await builderRequest(client, method, `${EMAIL_BUILDER_BASE}${path40}`, body ? { body } : {});
7472
7476
  if (!response.ok) {
7473
7477
  const text2 = await response.text();
7474
- throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path39}
7478
+ throw new Error(`Email Builder API Error ${response.status}: ${method} /emails/builder${path40}
7475
7479
  ${text2}`);
7476
7480
  }
7477
7481
  const text = await response.text();
@@ -11185,11 +11189,11 @@ async function readPageFull(client, pageId, fetcher = fetch) {
11185
11189
  const headers = await client.buildHeaders();
11186
11190
  headers.Origin = "https://app.gohighlevel.com";
11187
11191
  headers.Referer = "https://app.gohighlevel.com/";
11188
- const path39 = `/page/${pageId}?locationId=${client.locationId}`;
11189
- const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path39}`, { method: "GET", headers });
11192
+ const path40 = `/page/${pageId}?locationId=${client.locationId}`;
11193
+ const response = await fetcher(`https://backend.leadconnectorhq.com/funnels${path40}`, { method: "GET", headers });
11190
11194
  if (!response.ok) {
11191
11195
  const text2 = await response.text();
11192
- throw new Error(`Funnel API Error ${response.status}: GET ${path39}
11196
+ throw new Error(`Funnel API Error ${response.status}: GET ${path40}
11193
11197
  ${text2}`);
11194
11198
  }
11195
11199
  const text = await response.text();
@@ -11248,20 +11252,20 @@ function registerFunnelBuilderTools(server2, builderClient) {
11248
11252
  return;
11249
11253
  }
11250
11254
  const client = builderClient;
11251
- async function internalGet(path39) {
11252
- return client.request("GET", path39);
11255
+ async function internalGet(path40) {
11256
+ return client.request("GET", path40);
11253
11257
  }
11254
- async function internalPost(path39, body) {
11255
- return client.request("POST", path39, body);
11258
+ async function internalPost(path40, body) {
11259
+ return client.request("POST", path40, body);
11256
11260
  }
11257
- async function internalPut(path39, body) {
11258
- return client.request("PUT", path39, body);
11261
+ async function internalPut(path40, body) {
11262
+ return client.request("PUT", path40, body);
11259
11263
  }
11260
- async function internalDelete(path39) {
11261
- return client.request("DELETE", path39);
11264
+ async function internalDelete(path40) {
11265
+ return client.request("DELETE", path40);
11262
11266
  }
11263
- async function funnelRequest(method, path39, body) {
11264
- const url = `https://backend.leadconnectorhq.com/funnels${path39}`;
11267
+ async function funnelRequest(method, path40, body) {
11268
+ const url = `https://backend.leadconnectorhq.com/funnels${path40}`;
11265
11269
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
11266
11270
  const response = await builderRequest(client, method, url, {
11267
11271
  ...sendsBody ? { body } : {},
@@ -11274,7 +11278,7 @@ function registerFunnelBuilderTools(server2, builderClient) {
11274
11278
  });
11275
11279
  if (!response.ok) {
11276
11280
  const text2 = await response.text();
11277
- throw new Error(`Funnel API Error ${response.status}: ${method} ${path39}
11281
+ throw new Error(`Funnel API Error ${response.status}: ${method} ${path40}
11278
11282
  ${text2}`);
11279
11283
  }
11280
11284
  const text = await response.text();
@@ -11850,17 +11854,17 @@ function requireCompany(api) {
11850
11854
  }
11851
11855
  return company;
11852
11856
  }
11853
- async function call(api, path39, init) {
11857
+ async function call(api, path40, init) {
11854
11858
  const headers = { ...await api.headers(), Origin: "https://app.gohighlevel.com", Referer: "https://app.gohighlevel.com/" };
11855
- if (MUTATING_PATHS.some((mutating) => path39.startsWith(mutating))) {
11856
- assertWriteAllowed("POST", SVC + path39, { defaultLocationId: api.locationId() });
11859
+ if (MUTATING_PATHS.some((mutating) => path40.startsWith(mutating))) {
11860
+ assertWriteAllowed("POST", SVC + path40, { defaultLocationId: api.locationId() });
11857
11861
  }
11858
11862
  const doFetch = api.fetch ?? fetch;
11859
- const response = await doFetch(SVC + path39, { ...init, headers });
11863
+ const response = await doFetch(SVC + path40, { ...init, headers });
11860
11864
  const text = await response.text();
11861
11865
  if (!response.ok) {
11862
11866
  const hint = response.status === 401 && /not authorized to access this company/i.test(text) ? " \u2014 the Firebase session is authenticated as a DIFFERENT company than the one that owns this sub-account. Run switch_location for this sub-account and try again." : response.status === 401 ? " \u2014 the template library needs the workflow-builder Firebase session, not the API key alone. Run enable_workflow_builder for this sub-account." : "";
11863
- throw new Error(`GoHighLevel template library ${response.status} on ${path39}${hint}
11867
+ throw new Error(`GoHighLevel template library ${response.status} on ${path40}${hint}
11864
11868
  ${text.slice(0, 300)}`);
11865
11869
  }
11866
11870
  return text ? JSON.parse(text) : {};
@@ -16506,8 +16510,8 @@ function registerPageStudioTools(server2, builderClient) {
16506
16510
  }
16507
16511
  return { map: out, truncated };
16508
16512
  }
16509
- async function funnelRequest(method, path39, body) {
16510
- const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path39}`, {
16513
+ async function funnelRequest(method, path40, body) {
16514
+ const response = await builderRequest(client, method, `https://backend.leadconnectorhq.com/funnels${path40}`, {
16511
16515
  ...body ? { body } : {},
16512
16516
  headers: {
16513
16517
  // Internal-API calls require the app origin (autosave needs the
@@ -16516,7 +16520,7 @@ function registerPageStudioTools(server2, builderClient) {
16516
16520
  Referer: "https://app.gohighlevel.com/"
16517
16521
  }
16518
16522
  });
16519
- if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path39}
16523
+ if (!response.ok) throw new Error(`Funnel API Error ${response.status}: ${method} ${path40}
16520
16524
  ${await response.text()}`);
16521
16525
  const text = await response.text();
16522
16526
  return text ? JSON.parse(text) : {};
@@ -17120,13 +17124,13 @@ function cleanFormDataForSave(formData) {
17120
17124
  const cleaned = dropRedundantCaseCollidingKeys(formData);
17121
17125
  return isJsonObject(cleaned) ? cleaned : formData;
17122
17126
  }
17123
- async function formApiRequest(client, method, path39, body) {
17124
- const url = `https://backend.leadconnectorhq.com/forms${path39}`;
17127
+ async function formApiRequest(client, method, path40, body) {
17128
+ const url = `https://backend.leadconnectorhq.com/forms${path40}`;
17125
17129
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
17126
17130
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
17127
17131
  if (!response.ok) {
17128
17132
  const text2 = await response.text();
17129
- throw new Error(`Form API Error ${response.status}: ${method} ${path39}
17133
+ throw new Error(`Form API Error ${response.status}: ${method} ${path40}
17130
17134
  ${text2}`);
17131
17135
  }
17132
17136
  const text = await response.text();
@@ -17168,7 +17172,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17168
17172
  );
17169
17173
  return;
17170
17174
  }
17171
- const formRequest = (method, path39, body) => formApiRequest(client, method, path39, body);
17175
+ const formRequest = (method, path40, body) => formApiRequest(client, method, path40, body);
17172
17176
  server2.tool(
17173
17177
  "get_form_full",
17174
17178
  "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.",
@@ -17302,10 +17306,10 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17302
17306
  },
17303
17307
  async ({ formId, limit, skip }) => {
17304
17308
  try {
17305
- let path39 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
17306
- if (formId) path39 += `&formId=${formId}`;
17307
- if (skip) path39 += `&skip=${skip}`;
17308
- const result = await formRequest("GET", path39);
17309
+ let path40 = `/submissions?locationId=${client.locationId}&limit=${limit ?? 20}`;
17310
+ if (formId) path40 += `&formId=${formId}`;
17311
+ if (skip) path40 += `&skip=${skip}`;
17312
+ const result = await formRequest("GET", path40);
17309
17313
  return {
17310
17314
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
17311
17315
  };
@@ -17713,13 +17717,13 @@ var init_funnel_qa = __esm({
17713
17717
  function registerPipelineBuilderTools(server2, builderClient) {
17714
17718
  if (!builderClient) return;
17715
17719
  const client = builderClient;
17716
- async function pipelineRequest(method, path39, body) {
17717
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path39}`;
17720
+ async function pipelineRequest(method, path40, body) {
17721
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path40}`;
17718
17722
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT" || method === "PATCH");
17719
17723
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
17720
17724
  if (!response.ok) {
17721
17725
  const text2 = await response.text();
17722
- throw new Error(`Pipeline API Error ${response.status}: ${method} ${path39}
17726
+ throw new Error(`Pipeline API Error ${response.status}: ${method} ${path40}
17723
17727
  ${text2}`);
17724
17728
  }
17725
17729
  const text = await response.text();
@@ -18586,9 +18590,9 @@ function planFromPayload(payload) {
18586
18590
  return payload?.plan === "command-os" ? "command-os" : "mcp";
18587
18591
  }
18588
18592
  function legacyDeviceFingerprint() {
18589
- const os7 = require("node:os");
18593
+ const os8 = require("node:os");
18590
18594
  const crypto7 = require("node:crypto");
18591
- const raw = `${os7.hostname()}:${os7.userInfo().username}:${os7.platform()}:${os7.arch()}`;
18595
+ const raw = `${os8.hostname()}:${os8.userInfo().username}:${os8.platform()}:${os8.arch()}`;
18592
18596
  return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 16);
18593
18597
  }
18594
18598
  function deviceFingerprint(opts) {
@@ -23188,6 +23192,97 @@ var init_build_obligations = __esm({
23188
23192
  }
23189
23193
  });
23190
23194
 
23195
+ // src/claude-locate.ts
23196
+ function unusableShimMessage(shim) {
23197
+ return `Claude Code on this computer is an npm shim Command OS cannot start safely (${shim}). Install Claude Code with Anthropic's installer, then reopen the cockpit.`;
23198
+ }
23199
+ function runnable(p) {
23200
+ try {
23201
+ fs14.accessSync(p, fs14.constants.X_OK);
23202
+ return true;
23203
+ } catch {
23204
+ return false;
23205
+ }
23206
+ }
23207
+ function claudeFileNames(platform2, env) {
23208
+ if (platform2 !== "win32") return ["claude"];
23209
+ const exts = (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim().toLowerCase()).filter(Boolean);
23210
+ return [...exts.map((ext) => `claude${ext}`), "claude"];
23211
+ }
23212
+ function isShim(file2) {
23213
+ const ext = path13.extname(file2).toLowerCase();
23214
+ return ext === ".cmd" || ext === ".bat";
23215
+ }
23216
+ function shimTarget(shim) {
23217
+ let text;
23218
+ try {
23219
+ text = fs14.readFileSync(shim, "utf8");
23220
+ } catch {
23221
+ return null;
23222
+ }
23223
+ const refs = [...text.matchAll(/"%~?dp0%?\\([^"\r\n]+)"/g)].map((m) => m[1]);
23224
+ const resolve12 = (rel) => path13.join(path13.dirname(shim), ...rel.split(/[\\/]/));
23225
+ const script = refs.find((r) => /\.(?:js|cjs|mjs)$/i.test(r));
23226
+ if (script) {
23227
+ const p = resolve12(script);
23228
+ return fs14.existsSync(p) ? { kind: "script", path: p } : null;
23229
+ }
23230
+ const program = refs.find((r) => /\.exe$/i.test(r) && !/(^|[\\/])node\.exe$/i.test(r));
23231
+ if (program) {
23232
+ const p = resolve12(program);
23233
+ return fs14.existsSync(p) ? { kind: "program", path: p } : null;
23234
+ }
23235
+ return null;
23236
+ }
23237
+ function claudeBin(lookup = {}) {
23238
+ const platform2 = lookup.platform ?? process.platform;
23239
+ const env = lookup.env ?? process.env;
23240
+ const home = lookup.home ?? os3.homedir();
23241
+ const names = claudeFileNames(platform2, env);
23242
+ const dirs = (env.PATH || env.Path || "").split(platform2 === "win32" ? ";" : ":").map((d) => platform2 === "win32" ? d.replace(/^"|"$/g, "") : d).filter(Boolean);
23243
+ let unusableShim = null;
23244
+ for (const dir of dirs) {
23245
+ for (const name of names) {
23246
+ const candidate = path13.join(dir, name);
23247
+ if (!runnable(candidate)) continue;
23248
+ if (platform2 === "win32" && isShim(candidate) && !shimTarget(candidate)) {
23249
+ unusableShim ??= candidate;
23250
+ continue;
23251
+ }
23252
+ return candidate;
23253
+ }
23254
+ }
23255
+ const fallback = path13.join(home, ".local", "bin", platform2 === "win32" ? "claude.exe" : "claude");
23256
+ if (runnable(fallback)) return fallback;
23257
+ throw new Error(unusableShim ? unusableShimMessage(unusableShim) : NOT_FOUND_MESSAGE);
23258
+ }
23259
+ function claudeCommand(bin, args, lookup = {}) {
23260
+ const platform2 = lookup.platform ?? process.platform;
23261
+ if (platform2 !== "win32") return { command: bin, args };
23262
+ let resolved2 = bin;
23263
+ if (bin === "claude") {
23264
+ try {
23265
+ resolved2 = claudeBin(lookup);
23266
+ } catch {
23267
+ return { command: bin, args };
23268
+ }
23269
+ }
23270
+ if (!isShim(resolved2)) return { command: resolved2, args };
23271
+ const target = shimTarget(resolved2);
23272
+ if (!target) return { command: resolved2, args };
23273
+ return target.kind === "script" ? { command: lookup.execPath ?? process.execPath, args: [target.path, ...args] } : { command: target.path, args };
23274
+ }
23275
+ var fs14, os3, path13, NOT_FOUND_MESSAGE;
23276
+ var init_claude_locate = __esm({
23277
+ "src/claude-locate.ts"() {
23278
+ "use strict";
23279
+ fs14 = __toESM(require("node:fs"));
23280
+ os3 = __toESM(require("node:os"));
23281
+ path13 = __toESM(require("node:path"));
23282
+ NOT_FOUND_MESSAGE = "Claude Code CLI not found. Install it, then reopen the cockpit.";
23283
+ }
23284
+ });
23285
+
23191
23286
  // src/claude-cli.ts
23192
23287
  function parseVersion(text) {
23193
23288
  const m = /(\d+)\.(\d+)\.(\d+)/.exec(text || "");
@@ -23205,7 +23300,8 @@ function meetsFloor(version, floor = MIN_CLAUDE_VERSION) {
23205
23300
  }
23206
23301
  function readClaudeVersion(bin) {
23207
23302
  try {
23208
- const r = (0, import_child_process2.spawnSync)(bin, ["--version"], { encoding: "utf8", timeout: 15e3 });
23303
+ const cmd = claudeCommand(bin, ["--version"]);
23304
+ const r = (0, import_child_process2.spawnSync)(cmd.command, cmd.args, { encoding: "utf8", timeout: 15e3 });
23209
23305
  if (r.error || r.status !== 0) return null;
23210
23306
  const parsed2 = parseVersion(`${r.stdout ?? ""} ${r.stderr ?? ""}`);
23211
23307
  return parsed2 ? `${parsed2.major}.${parsed2.minor}.${parsed2.patch}` : null;
@@ -23218,21 +23314,21 @@ function tooOldMessage(found) {
23218
23314
  return `Command OS needs Claude Code ${MIN_CLAUDE_VERSION} or newer to run a stage. ${seen} Update Claude Code, then reopen Command OS. Nothing was started and nothing was spent.`;
23219
23315
  }
23220
23316
  function enginePath() {
23221
- return path13.join(appDataDir(), "command-os-engine.json");
23317
+ return path14.join(appDataDir(), "command-os-engine.json");
23222
23318
  }
23223
23319
  function readEngineRecord(file2 = enginePath()) {
23224
23320
  try {
23225
- const r = JSON.parse(fs14.readFileSync(file2, "utf8"));
23321
+ const r = JSON.parse(fs15.readFileSync(file2, "utf8"));
23226
23322
  return typeof r?.version === "string" ? r : null;
23227
23323
  } catch {
23228
23324
  return null;
23229
23325
  }
23230
23326
  }
23231
23327
  function writeEngineRecord(rec, file2 = enginePath()) {
23232
- fs14.mkdirSync(path13.dirname(file2), { recursive: true });
23233
- fs14.writeFileSync(file2, JSON.stringify(rec, null, 2), { mode: 384 });
23328
+ fs15.mkdirSync(path14.dirname(file2), { recursive: true });
23329
+ fs15.writeFileSync(file2, JSON.stringify(rec, null, 2), { mode: 384 });
23234
23330
  try {
23235
- fs14.chmodSync(file2, 384);
23331
+ fs15.chmodSync(file2, 384);
23236
23332
  } catch {
23237
23333
  }
23238
23334
  }
@@ -23263,7 +23359,7 @@ function runSmoke(bin, timeoutMs = 9e4) {
23263
23359
  resolve12(r);
23264
23360
  }
23265
23361
  };
23266
- const child = (0, import_child_process2.spawn)(bin, [
23362
+ const cmd = claudeCommand(bin, [
23267
23363
  "-p",
23268
23364
  SMOKE_PROMPT,
23269
23365
  "--output-format",
@@ -23276,7 +23372,8 @@ function runSmoke(bin, timeoutMs = 9e4) {
23276
23372
  // model needs no tool to answer with one line.
23277
23373
  "--disallowedTools",
23278
23374
  "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch,Task"
23279
- ], { stdio: ["ignore", "pipe", "pipe"] });
23375
+ ]);
23376
+ const child = (0, import_child_process2.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "pipe"] });
23280
23377
  const timer = setTimeout(() => {
23281
23378
  child.kill("SIGKILL");
23282
23379
  done({ ok: false, error: "the engine check did not finish in time" });
@@ -23370,14 +23467,15 @@ async function ensureEngineReady(bin, onNote = () => {
23370
23467
  onNote(`Claude Code ${version} checked out${smoke.model ? ` (${smoke.model})` : ""}. Starting the step.`);
23371
23468
  return { ok: true, version, smoked: true, smokeCostUsd: smoke.costUsd, smokeModel: smoke.model };
23372
23469
  }
23373
- var import_child_process2, fs14, path13, MIN_CLAUDE_VERSION, SMOKE_PROMPT;
23470
+ var import_child_process2, fs15, path14, MIN_CLAUDE_VERSION, SMOKE_PROMPT;
23374
23471
  var init_claude_cli = __esm({
23375
23472
  "src/claude-cli.ts"() {
23376
23473
  "use strict";
23377
23474
  import_child_process2 = require("child_process");
23378
- fs14 = __toESM(require("fs"));
23379
- path13 = __toESM(require("path"));
23475
+ fs15 = __toESM(require("fs"));
23476
+ path14 = __toESM(require("path"));
23380
23477
  init_credentials_store();
23478
+ init_claude_locate();
23381
23479
  MIN_CLAUDE_VERSION = "2.1.0";
23382
23480
  SMOKE_PROMPT = 'Reply with exactly this one line and nothing else: RESULT: {"ok":true,"summary":"engine check"}';
23383
23481
  }
@@ -23545,17 +23643,17 @@ var init_stage_boundary = __esm({
23545
23643
  // src/plan-guide.ts
23546
23644
  function skillRoot() {
23547
23645
  const candidates = [
23548
- path14.join(__dirname, "..", "skills", "blueprint"),
23646
+ path15.join(__dirname, "..", "skills", "blueprint"),
23549
23647
  // dist/ → package root
23550
- path14.join(__dirname, "..", "..", "skills", "blueprint"),
23648
+ path15.join(__dirname, "..", "..", "skills", "blueprint"),
23551
23649
  // src/ in dev
23552
- path14.join(process.cwd(), "skills", "blueprint")
23650
+ path15.join(process.cwd(), "skills", "blueprint")
23553
23651
  ];
23554
- return candidates.find((c) => fs15.existsSync(path14.join(c, "references", "build-plan-schema.md"))) ?? null;
23652
+ return candidates.find((c) => fs16.existsSync(path15.join(c, "references", "build-plan-schema.md"))) ?? null;
23555
23653
  }
23556
23654
  function readIf(p, max) {
23557
23655
  try {
23558
- const t = fs15.readFileSync(p, "utf8");
23656
+ const t = fs16.readFileSync(p, "utf8");
23559
23657
  return t.length > max ? t.slice(0, max) : t;
23560
23658
  } catch {
23561
23659
  return null;
@@ -23568,12 +23666,12 @@ function presetForIndustry(industry) {
23568
23666
  function loadPlanGuide(industry) {
23569
23667
  const root = skillRoot();
23570
23668
  if (!root) return "";
23571
- const schema = readIf(path14.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
23669
+ const schema = readIf(path15.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
23572
23670
  const presetName = presetForIndustry(industry);
23573
- const preset = readIf(path14.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
23671
+ const preset = readIf(path15.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
23574
23672
  const exampleFile = EXAMPLE_FOR_PRESET[presetName] ?? "sample-build-plan.json";
23575
- const example = readIf(path14.join(root, "examples", exampleFile), CAP_EXAMPLE);
23576
- const copyGuide = readIf(path14.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
23673
+ const example = readIf(path15.join(root, "examples", exampleFile), CAP_EXAMPLE);
23674
+ const copyGuide = readIf(path15.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
23577
23675
  if (!schema) return "";
23578
23676
  return `
23579
23677
  PLAN GUIDE \u2014 everything you need to write the build plan is below. Do NOT try to read files; you cannot.
@@ -23591,12 +23689,12 @@ ${example}
23591
23689
  ` : "") + `=== End of plan guide ===
23592
23690
  `;
23593
23691
  }
23594
- var fs15, path14, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
23692
+ var fs16, path15, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
23595
23693
  var init_plan_guide = __esm({
23596
23694
  "src/plan-guide.ts"() {
23597
23695
  "use strict";
23598
- fs15 = __toESM(require("fs"));
23599
- path14 = __toESM(require("path"));
23696
+ fs16 = __toESM(require("fs"));
23697
+ path15 = __toESM(require("path"));
23600
23698
  PRESET_FOR_INDUSTRY = {
23601
23699
  "med-spa": "med-spa",
23602
23700
  clinic: "clinic",
@@ -26210,10 +26308,10 @@ function progressLine(e) {
26210
26308
  }
26211
26309
  }
26212
26310
  function defaultProgressDir(base = appDataDir()) {
26213
- return path15.join(base, "progress");
26311
+ return path16.join(base, "progress");
26214
26312
  }
26215
26313
  function progressFilePath(dir, locationId2) {
26216
- return path15.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
26314
+ return path16.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
26217
26315
  }
26218
26316
  function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DIR_ENV]) {
26219
26317
  if (!dir) return null;
@@ -26221,10 +26319,10 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
26221
26319
  const file2 = progressFilePath(dir, locationId2);
26222
26320
  const write = () => {
26223
26321
  try {
26224
- fs16.mkdirSync(dir, { recursive: true, mode: 448 });
26322
+ fs17.mkdirSync(dir, { recursive: true, mode: 448 });
26225
26323
  const tmp = `${file2}.tmp`;
26226
- fs16.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
26227
- fs16.renameSync(tmp, file2);
26324
+ fs17.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
26325
+ fs17.renameSync(tmp, file2);
26228
26326
  } catch {
26229
26327
  }
26230
26328
  };
@@ -26242,7 +26340,7 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
26242
26340
  }
26243
26341
  function readProgressSnapshot(dir, locationId2) {
26244
26342
  try {
26245
- const raw = JSON.parse(fs16.readFileSync(progressFilePath(dir, locationId2), "utf8"));
26343
+ const raw = JSON.parse(fs17.readFileSync(progressFilePath(dir, locationId2), "utf8"));
26246
26344
  const p = raw?.progress;
26247
26345
  if (!p || !Array.isArray(p.groups) || typeof p.updatedAt !== "string") return null;
26248
26346
  return p;
@@ -26263,12 +26361,12 @@ function tapPush(arr, onItem) {
26263
26361
  };
26264
26362
  Object.defineProperty(arr, "push", { value: tapped, writable: true, configurable: true, enumerable: false });
26265
26363
  }
26266
- var fs16, path15, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
26364
+ var fs17, path16, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
26267
26365
  var init_build_progress = __esm({
26268
26366
  "src/build-progress.ts"() {
26269
26367
  "use strict";
26270
- fs16 = __toESM(require("fs"));
26271
- path15 = __toESM(require("path"));
26368
+ fs17 = __toESM(require("fs"));
26369
+ path16 = __toESM(require("path"));
26272
26370
  init_executor();
26273
26371
  init_credentials_store();
26274
26372
  PROGRESS_GROUPS = [
@@ -26406,10 +26504,10 @@ var init_build_progress = __esm({
26406
26504
 
26407
26505
  // src/build-journal.ts
26408
26506
  function journalDir(base = appDataDir()) {
26409
- return path16.join(base, "build-journal");
26507
+ return path17.join(base, "build-journal");
26410
26508
  }
26411
26509
  function journalPath(locationId2, base) {
26412
- return path16.join(journalDir(base), `${locationId2}.jsonl`);
26510
+ return path17.join(journalDir(base), `${locationId2}.jsonl`);
26413
26511
  }
26414
26512
  function newRunId(now = /* @__PURE__ */ new Date(), rand = defaultRand) {
26415
26513
  const iso2 = now.toISOString();
@@ -26469,16 +26567,16 @@ function buildRunRecord(input) {
26469
26567
  }
26470
26568
  function appendJournal(record, base) {
26471
26569
  const dir = journalDir(base);
26472
- fs17.mkdirSync(dir, { recursive: true, mode: 448 });
26570
+ fs18.mkdirSync(dir, { recursive: true, mode: 448 });
26473
26571
  const file2 = journalPath(record.locationId, base);
26474
- fs17.appendFileSync(file2, `${JSON.stringify(record)}
26572
+ fs18.appendFileSync(file2, `${JSON.stringify(record)}
26475
26573
  `, { mode: 384 });
26476
26574
  return file2;
26477
26575
  }
26478
26576
  function readJournal(locationId2, base) {
26479
26577
  let raw;
26480
26578
  try {
26481
- raw = fs17.readFileSync(journalPath(locationId2, base), "utf8");
26579
+ raw = fs18.readFileSync(journalPath(locationId2, base), "utf8");
26482
26580
  } catch {
26483
26581
  return [];
26484
26582
  }
@@ -26522,12 +26620,12 @@ function describeRuns(locationId2, base) {
26522
26620
  reverted: reverted.has(r.runId)
26523
26621
  })).reverse();
26524
26622
  }
26525
- var fs17, path16;
26623
+ var fs18, path17;
26526
26624
  var init_build_journal = __esm({
26527
26625
  "src/build-journal.ts"() {
26528
26626
  "use strict";
26529
- fs17 = __toESM(require("fs"));
26530
- path16 = __toESM(require("path"));
26627
+ fs18 = __toESM(require("fs"));
26628
+ path17 = __toESM(require("path"));
26531
26629
  init_credentials_store();
26532
26630
  }
26533
26631
  });
@@ -26578,24 +26676,6 @@ function stageBlockedBy(stages, stage) {
26578
26676
  }
26579
26677
  return null;
26580
26678
  }
26581
- function claudeBin() {
26582
- const fallback = path17.join(os3.homedir(), ".local", "bin", "claude");
26583
- const fromPath = (process.env.PATH || "").split(path17.delimiter).map((d) => path17.join(d, "claude")).find((p) => {
26584
- try {
26585
- fs18.accessSync(p, fs18.constants.X_OK);
26586
- return true;
26587
- } catch {
26588
- return false;
26589
- }
26590
- });
26591
- if (fromPath) return fromPath;
26592
- try {
26593
- fs18.accessSync(fallback, fs18.constants.X_OK);
26594
- return fallback;
26595
- } catch {
26596
- }
26597
- throw new Error("Claude Code CLI not found. Install it, then reopen the cockpit.");
26598
- }
26599
26679
  function verifyReadbackPrompt(ctx) {
26600
26680
  return "\nAccount read-back has already run in code. These records decide existence, regardless of RESULT ok or dependencyChecks: " + JSON.stringify(ctx?.readback ?? []) + "\nDo not contradict any record. Keep dependencyChecks for evidence you establish yourself. The final report replaces the standing Checked and Not checked headings with the deterministic roll-call. Use the live URL on the funnel or page record, and the triggerTags and id on the speed-to-lead workflow record, for the existing fake-client check. Do not invent a URL or a trigger tag. Before calling verify_funnel, disclose that it submits a marked test lead and may fire this client's automations once. Preserve the existing verify_funnel submission and cleanup. If the funnel has no working page, report the reason that live proof could not run. Add liveProof:{ok,evidence} to RESULT. Set liveProof.ok true only when verify_funnel reports that the marked lead arrived and the test contact was deleted. Quote that result's evidence, including its contact id and cleanup result. Otherwise set liveProof.ok false with the check's own failure or the reason it could not run.\n";
26601
26681
  }
@@ -26662,11 +26742,12 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
26662
26742
  const progressDir = opts.onProgress ? defaultProgressDir() : void 0;
26663
26743
  if (progressDir) {
26664
26744
  try {
26665
- fs18.rmSync(progressFilePath(progressDir, locationId2), { force: true });
26745
+ fs19.rmSync(progressFilePath(progressDir, locationId2), { force: true });
26666
26746
  } catch {
26667
26747
  }
26668
26748
  }
26669
- const child = (0, import_child_process3.spawn)(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context), {
26749
+ const cmd = claudeCommand(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context));
26750
+ const child = (0, import_child_process3.spawn)(cmd.command, cmd.args, {
26670
26751
  stdio: ["ignore", "pipe", "pipe"],
26671
26752
  env: { ...process.env, ...progressDir ? { [PROGRESS_DIR_ENV]: progressDir } : {}, GHL_COMMAND_STAGE: String(stage), GHL_COMMAND_INTAKE_HASH: opts.context?.inputHash ?? "" }
26672
26753
  });
@@ -26775,21 +26856,20 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
26775
26856
  });
26776
26857
  });
26777
26858
  }
26778
- var import_child_process3, fs18, os3, path17, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
26859
+ var import_child_process3, fs19, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
26779
26860
  var init_stage_runner = __esm({
26780
26861
  "src/stage-runner.ts"() {
26781
26862
  "use strict";
26782
26863
  init_intake_notification();
26783
26864
  import_child_process3 = require("child_process");
26784
- fs18 = __toESM(require("fs"));
26785
- os3 = __toESM(require("os"));
26786
- path17 = __toESM(require("path"));
26865
+ fs19 = __toESM(require("fs"));
26787
26866
  init_stage_boundary();
26788
26867
  init_plan_guide();
26789
26868
  init_question_set();
26790
26869
  init_build_progress();
26791
26870
  init_build_journal();
26792
26871
  init_claude_cli();
26872
+ init_claude_locate();
26793
26873
  init_account_rules();
26794
26874
  init_account_rules();
26795
26875
  STAFF_NOTIFICATION_RULE = `Staff notifications ALWAYS stay in the workflow. Call get_users on THIS account and use only a user id it returns as the recipient. If get_users returns no users, leave the recipient EMPTY ("") \u2014 never use a user id from another account, from the agency, from an earlier conversation or from memory (a foreign id looks like it worked and notifies nobody). An empty recipient can make publish fail: then keep the workflow as a draft with the notification step still in it, and list that step in the RESULT issues as "waiting for a staff member: add one in Settings \u2192 My Staff, then pick them on the notification step and publish".`;
@@ -27254,17 +27334,17 @@ function runCostPath() {
27254
27334
  return path18.join(appDataDir(), "command-os-run-cost.jsonl");
27255
27335
  }
27256
27336
  function recordRunCost(rec, file2 = runCostPath()) {
27257
- fs19.mkdirSync(path18.dirname(file2), { recursive: true });
27258
- fs19.appendFileSync(file2, `${JSON.stringify(rec)}
27337
+ fs20.mkdirSync(path18.dirname(file2), { recursive: true });
27338
+ fs20.appendFileSync(file2, `${JSON.stringify(rec)}
27259
27339
  `, { mode: 384 });
27260
27340
  try {
27261
- fs19.chmodSync(file2, 384);
27341
+ fs20.chmodSync(file2, 384);
27262
27342
  } catch {
27263
27343
  }
27264
27344
  }
27265
27345
  function readRunCosts(file2 = runCostPath()) {
27266
27346
  try {
27267
- return fs19.readFileSync(file2, "utf8").split("\n").filter((l) => l.trim()).map((l) => {
27347
+ return fs20.readFileSync(file2, "utf8").split("\n").filter((l) => l.trim()).map((l) => {
27268
27348
  try {
27269
27349
  return JSON.parse(l);
27270
27350
  } catch {
@@ -27295,11 +27375,11 @@ function costLines(o) {
27295
27375
  if (o.engineCheckRan && o.engineCheckCostUsd === void 0) out.push("Claude readiness check: cost not reported.");
27296
27376
  return out;
27297
27377
  }
27298
- var fs19, path18;
27378
+ var fs20, path18;
27299
27379
  var init_run_cost = __esm({
27300
27380
  "src/run-cost.ts"() {
27301
27381
  "use strict";
27302
- fs19 = __toESM(require("fs"));
27382
+ fs20 = __toESM(require("fs"));
27303
27383
  path18 = __toESM(require("path"));
27304
27384
  init_credentials_store();
27305
27385
  }
@@ -27314,7 +27394,8 @@ function callClaudeRecorded(bin, argv, context, onCost = () => {
27314
27394
  let stderr = "";
27315
27395
  let facts = {};
27316
27396
  let settled = false;
27317
- const child = (0, import_node_child_process2.spawn)(bin, [...argv, "--output-format", "stream-json", "--verbose"], { stdio: ["ignore", "pipe", "pipe"] });
27397
+ const cmd = claudeCommand(bin, [...argv, "--output-format", "stream-json", "--verbose"]);
27398
+ const child = (0, import_node_child_process2.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "pipe"] });
27318
27399
  const timer = setTimeout(() => child.kill("SIGKILL"), 6 * 60 * 1e3);
27319
27400
  const finish3 = (ok) => {
27320
27401
  if (settled) return;
@@ -27365,6 +27446,7 @@ var init_claude_call = __esm({
27365
27446
  import_node_child_process2 = require("node:child_process");
27366
27447
  init_run_cost();
27367
27448
  init_claude_cli();
27449
+ init_claude_locate();
27368
27450
  }
27369
27451
  });
27370
27452
 
@@ -28127,14 +28209,14 @@ function plansDir(base = appDataDir()) {
28127
28209
  }
28128
28210
  function savePlanRecord(rec, base) {
28129
28211
  const dir = plansDir(base);
28130
- fs20.mkdirSync(dir, { recursive: true, mode: 448 });
28212
+ fs21.mkdirSync(dir, { recursive: true, mode: 448 });
28131
28213
  const file2 = path19.join(dir, `${rec.locationId}.json`);
28132
- fs20.writeFileSync(file2, JSON.stringify({ ...rec, contentHash: contentHash(rec.plan), inputHash: rec.inputHash ?? process.env.GHL_COMMAND_INTAKE_HASH, dependencies: rec.dependencies ?? planDependencies(rec.plan) }, null, 2), { mode: 384 });
28214
+ fs21.writeFileSync(file2, JSON.stringify({ ...rec, contentHash: contentHash(rec.plan), inputHash: rec.inputHash ?? process.env.GHL_COMMAND_INTAKE_HASH, dependencies: rec.dependencies ?? planDependencies(rec.plan) }, null, 2), { mode: 384 });
28133
28215
  return file2;
28134
28216
  }
28135
28217
  function readSavedPlan(locationId2, base) {
28136
28218
  try {
28137
- return JSON.parse(fs20.readFileSync(path19.join(plansDir(base), `${locationId2}.json`), "utf8"));
28219
+ return JSON.parse(fs21.readFileSync(path19.join(plansDir(base), `${locationId2}.json`), "utf8"));
28138
28220
  } catch {
28139
28221
  return null;
28140
28222
  }
@@ -28157,11 +28239,11 @@ function planSummary(rec) {
28157
28239
  if (wfs.length) parts.push(`workflows: ${wfs.map((w) => `${w.name} (${Array.isArray(w.actions) ? w.actions.length : "?"} actions)`).join("; ")}`);
28158
28240
  return parts.join("\n") || "(plan has no recognisable sections)";
28159
28241
  }
28160
- var fs20, path19;
28242
+ var fs21, path19;
28161
28243
  var init_plan_store = __esm({
28162
28244
  "src/plan-store.ts"() {
28163
28245
  "use strict";
28164
- fs20 = __toESM(require("fs"));
28246
+ fs21 = __toESM(require("fs"));
28165
28247
  path19 = __toESM(require("path"));
28166
28248
  init_credentials_store();
28167
28249
  init_build_contract();
@@ -28179,7 +28261,8 @@ function askNoTools(prompt, opts = {}) {
28179
28261
  resolve12("");
28180
28262
  return;
28181
28263
  }
28182
- const child = (0, import_node_child_process3.spawn)(bin, ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", String(opts.maxTurns ?? 3), "--allowedTools", "", "--disallowedTools", NO_TOOLS_DENY], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } });
28264
+ const cmd = claudeCommand(bin, ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", String(opts.maxTurns ?? 3), "--allowedTools", "", "--disallowedTools", NO_TOOLS_DENY]);
28265
+ const child = (0, import_node_child_process3.spawn)(cmd.command, cmd.args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } });
28183
28266
  const timeout = setTimeout(() => child.kill("SIGKILL"), opts.timeoutMs ?? 3 * 60 * 1e3);
28184
28267
  let lastText = "";
28185
28268
  let buffered = "";
@@ -28228,6 +28311,7 @@ var init_handoff_run = __esm({
28228
28311
  "use strict";
28229
28312
  import_node_child_process3 = require("node:child_process");
28230
28313
  init_stage_runner();
28314
+ init_claude_locate();
28231
28315
  init_handoff_pack();
28232
28316
  CLIENT_SENTENCE_RULES = "Write for the business owner, not a developer. Complete sentences that end with a period; never trail off. One or two sentences per item, no more. Everyday words: say 'text messaging registration (A2P)' not 'A2P'; 'the calendar is not connected to Google or Outlook yet' not 'OAuth'; 'nothing was wrong' not 'status ok'. No tool names, no ids, no field names in code style, no JSON, no arrows. Keep the names of workflows, pipelines and forms because the client will recognise them. If a list is long, name the first few and say how many in total. State what is in place, or what is missing and why, plainly. An item marked 'done but not confirmed' has NOT been confirmed: never describe it as having happened or been observed; say what is still to be confirmed and how (for example, 'this is confirmed on the handoff call, when you complete one task in the account without help').";
28233
28317
  NO_TOOLS_DENY = "Bash,Write,Edit,MultiEdit,NotebookEdit,WebFetch,WebSearch,Task,TodoWrite,Read,Glob,Grep";
@@ -28392,7 +28476,7 @@ var init_client_capabilities = __esm({
28392
28476
  { key: "reputation", label: "Reputation and content", blurb: "Reviews, social posts and the blog." },
28393
28477
  { key: "health", label: "Health and handoff", blurb: "Proving it works, keeping it clean, and handing it over." }
28394
28478
  ];
28395
- N = (label, path39, params, key, nameField, version) => ({ label, path: path39, params, key, nameField, version });
28479
+ N = (label, path40, params, key, nameField, version) => ({ label, path: path40, params, key, nameField, version });
28396
28480
  AI2 = "2021-04-15";
28397
28481
  CAPABILITIES = [
28398
28482
  // ── Set up ──────────────────────────────────────────────────────────────
@@ -28653,7 +28737,7 @@ var require_package = __commonJS({
28653
28737
  "package.json"(exports2, module2) {
28654
28738
  module2.exports = {
28655
28739
  name: "@elitedcs/ghl-mcp",
28656
- version: "3.83.8",
28740
+ version: "3.83.9",
28657
28741
  mcpName: "io.github.drjerryrelth/ghl-command",
28658
28742
  description: "GoHighLevel MCP Server for Claude. 250 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
28659
28743
  main: "dist/index.js",
@@ -28752,17 +28836,17 @@ var require_package = __commonJS({
28752
28836
  });
28753
28837
 
28754
28838
  // src/package-version.ts
28755
- var fs21, path20, bundledPkg, pkg;
28839
+ var fs22, path20, bundledPkg, pkg;
28756
28840
  var init_package_version = __esm({
28757
28841
  "src/package-version.ts"() {
28758
28842
  "use strict";
28759
- fs21 = __toESM(require("fs"));
28843
+ fs22 = __toESM(require("fs"));
28760
28844
  path20 = __toESM(require("path"));
28761
28845
  bundledPkg = require_package();
28762
28846
  pkg = (() => {
28763
28847
  try {
28764
28848
  const onDisk = JSON.parse(
28765
- fs21.readFileSync(path20.resolve(__dirname, "..", "package.json"), "utf8")
28849
+ fs22.readFileSync(path20.resolve(__dirname, "..", "package.json"), "utf8")
28766
28850
  );
28767
28851
  if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
28768
28852
  return { version: onDisk.version };
@@ -29034,7 +29118,7 @@ function sanitizeEngagements(raw) {
29034
29118
  }
29035
29119
  function readClientEngagements(file2 = clientEngagementsPath()) {
29036
29120
  try {
29037
- const raw = JSON.parse(fs22.readFileSync(file2, "utf8"));
29121
+ const raw = JSON.parse(fs23.readFileSync(file2, "utf8"));
29038
29122
  if (raw && typeof raw === "object") return sanitizeEngagements(raw);
29039
29123
  } catch {
29040
29124
  }
@@ -29061,7 +29145,7 @@ function writeClientEngagement(locationId2, patch, file2 = clientEngagementsPath
29061
29145
  if (!id || FORBIDDEN_KEY.has(id)) throw new Error("An id is required.");
29062
29146
  const stamp = () => {
29063
29147
  try {
29064
- return fs22.readFileSync(file2, "utf8");
29148
+ return fs23.readFileSync(file2, "utf8");
29065
29149
  } catch {
29066
29150
  return "";
29067
29151
  }
@@ -29081,20 +29165,20 @@ function writeClientEngagement(locationId2, patch, file2 = clientEngagementsPath
29081
29165
  };
29082
29166
  const persist = (next) => {
29083
29167
  if (file2 === clientEngagementsPath() && !process.env[CLIENT_ENGAGEMENTS_ENV]) ensureAppDataDir();
29084
- fs22.mkdirSync(path21.dirname(file2), { recursive: true });
29168
+ fs23.mkdirSync(path21.dirname(file2), { recursive: true });
29085
29169
  const tmp = `${file2}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
29086
29170
  try {
29087
- fs22.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
29171
+ fs23.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
29088
29172
  if (process.platform !== "win32") {
29089
29173
  try {
29090
- fs22.chmodSync(tmp, 384);
29174
+ fs23.chmodSync(tmp, 384);
29091
29175
  } catch {
29092
29176
  }
29093
29177
  }
29094
- fs22.renameSync(tmp, file2);
29178
+ fs23.renameSync(tmp, file2);
29095
29179
  } catch (e) {
29096
29180
  try {
29097
- fs22.unlinkSync(tmp);
29181
+ fs23.unlinkSync(tmp);
29098
29182
  } catch {
29099
29183
  }
29100
29184
  throw e;
@@ -29166,11 +29250,11 @@ function monthlyFloor(roster, profile) {
29166
29250
  byKind
29167
29251
  };
29168
29252
  }
29169
- var fs22, path21, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, ENGAGEMENT_KINDS, DEFAULT_ENGAGEMENT_KIND, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
29253
+ var fs23, path21, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, ENGAGEMENT_KINDS, DEFAULT_ENGAGEMENT_KIND, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
29170
29254
  var init_client_engagements = __esm({
29171
29255
  "src/client-engagements.ts"() {
29172
29256
  "use strict";
29173
- fs22 = __toESM(require("fs"));
29257
+ fs23 = __toESM(require("fs"));
29174
29258
  path21 = __toESM(require("path"));
29175
29259
  init_credentials_store();
29176
29260
  init_field_sanitize();
@@ -29189,7 +29273,7 @@ var init_client_engagements = __esm({
29189
29273
  // src/agency-profile-page.ts
29190
29274
  function agencyProfilePageHtml(p, clients = [], seatToken = "") {
29191
29275
  const offers = (p.offers ?? []).map((o) => `${o.name}${o.priceFloor ? ` - ${o.priceFloor}${o.cadence === "monthly" ? "/mo" : ""}` : ""}`).join("\n");
29192
- const os7 = p.agencyOs ?? {};
29276
+ const os8 = p.agencyOs ?? {};
29193
29277
  const lines = (xs) => (xs ?? []).join("\n");
29194
29278
  const sops = (p.delivery?.sops ?? []).join("\n");
29195
29279
  const staff = (p.staff ?? []).map((m) => [m.name, m.role, m.email].filter(Boolean).join(" \xB7 ")).join("\n");
@@ -29301,50 +29385,50 @@ ${cockpitHeaderHtml("agency")}
29301
29385
  <fieldset><legend>Your brand: who you are</legend>
29302
29386
  <p class="hint">Filled in once, in your own words. Used on the assessment report, in proposals, in every Claude session's opening brief, and by every Command OS build.</p>
29303
29387
  <label for="story">Your story: why this agency exists, in two or three sentences</label>
29304
- <textarea id="story" name="story" placeholder="I ran a clinic for twelve years and watched good practices lose patients to voicemail\u2026">${esc4(os7.story)}</textarea>
29388
+ <textarea id="story" name="story" placeholder="I ran a clinic for twelve years and watched good practices lose patients to voicemail\u2026">${esc4(os8.story)}</textarea>
29305
29389
  <label for="method">Your method: how you get results, and what you call it</label>
29306
- <textarea id="method" name="method" placeholder="The Full Room method: one weekend, a full calendar, a system that keeps booking after we leave">${esc4(os7.method)}</textarea>
29390
+ <textarea id="method" name="method" placeholder="The Full Room method: one weekend, a full calendar, a system that keeps booking after we leave">${esc4(os8.method)}</textarea>
29307
29391
  <div class="two">
29308
- <div><label for="voice">How you sound: three words, then a sentence</label><textarea id="voice" name="voice" placeholder="Direct, warm, clinical. Specifics over hype.">${esc4(os7.voice)}</textarea></div>
29309
- <div><label for="goals">Your goal for the next twelve months</label><textarea id="goals" name="goals" placeholder="$50k a month from 51 subscribers">${esc4(os7.goals)}</textarea></div>
29392
+ <div><label for="voice">How you sound: three words, then a sentence</label><textarea id="voice" name="voice" placeholder="Direct, warm, clinical. Specifics over hype.">${esc4(os8.voice)}</textarea></div>
29393
+ <div><label for="goals">Your goal for the next twelve months</label><textarea id="goals" name="goals" placeholder="$50k a month from 51 subscribers">${esc4(os8.goals)}</textarea></div>
29310
29394
  </div>
29311
29395
  <label for="values">What you stand for and what you refuse to do: one per line</label>
29312
- <textarea id="values" name="values" placeholder="Never a false all-clear&#10;No cold texting anyone who did not opt in">${esc4(lines(os7.values))}</textarea>
29396
+ <textarea id="values" name="values" placeholder="Never a false all-clear&#10;No cold texting anyone who did not opt in">${esc4(lines(os8.values))}</textarea>
29313
29397
  <label for="proof">Proof you may state, with permission: one per line</label>
29314
- <textarea id="proof" name="proof" placeholder="Radiance Med Spa: 34 consults booked in one weekend (permission on file)">${esc4(lines(os7.proof))}</textarea>
29398
+ <textarea id="proof" name="proof" placeholder="Radiance Med Spa: 34 consults booked in one weekend (permission on file)">${esc4(lines(os8.proof))}</textarea>
29315
29399
  </fieldset>
29316
29400
 
29317
29401
  <fieldset><legend>Ideal client: who you serve</legend>
29318
29402
  <label for="icaWho">Who they are: role, business, size, where</label>
29319
- <textarea id="icaWho" name="icaWho" placeholder="The owner of a two-provider med spa in a mid-size US city, doing $40k a month and answering the phone herself">${esc4(os7.ica?.who)}</textarea>
29403
+ <textarea id="icaWho" name="icaWho" placeholder="The owner of a two-provider med spa in a mid-size US city, doing $40k a month and answering the phone herself">${esc4(os8.ica?.who)}</textarea>
29320
29404
  <div class="two">
29321
- <div><label for="icaPains">Their pains, worst first: one per line</label><textarea id="icaPains" name="icaPains">${esc4(lines(os7.ica?.pains))}</textarea></div>
29322
- <div><label for="icaDesires">What they want, most first: one per line</label><textarea id="icaDesires" name="icaDesires">${esc4(lines(os7.ica?.desires))}</textarea></div>
29405
+ <div><label for="icaPains">Their pains, worst first: one per line</label><textarea id="icaPains" name="icaPains">${esc4(lines(os8.ica?.pains))}</textarea></div>
29406
+ <div><label for="icaDesires">What they want, most first: one per line</label><textarea id="icaDesires" name="icaDesires">${esc4(lines(os8.ica?.desires))}</textarea></div>
29323
29407
  </div>
29324
29408
  <div class="two">
29325
- <div><label for="icaObjections">Why they say no: one per line</label><textarea id="icaObjections" name="icaObjections">${esc4(lines(os7.ica?.objections))}</textarea></div>
29326
- <div><label for="icaTriggers">What makes them buy now: one per line</label><textarea id="icaTriggers" name="icaTriggers">${esc4(lines(os7.ica?.triggers))}</textarea></div>
29409
+ <div><label for="icaObjections">Why they say no: one per line</label><textarea id="icaObjections" name="icaObjections">${esc4(lines(os8.ica?.objections))}</textarea></div>
29410
+ <div><label for="icaTriggers">What makes them buy now: one per line</label><textarea id="icaTriggers" name="icaTriggers">${esc4(lines(os8.ica?.triggers))}</textarea></div>
29327
29411
  </div>
29328
29412
  <div class="two">
29329
- <div><label for="icaVocabulary">Their own words for the problem: one per line</label><textarea id="icaVocabulary" name="icaVocabulary">${esc4(lines(os7.ica?.vocabulary))}</textarea></div>
29330
- <div><label for="icaHangouts">Where they read, listen and gather: one per line</label><textarea id="icaHangouts" name="icaHangouts">${esc4(lines(os7.ica?.hangouts))}</textarea></div>
29413
+ <div><label for="icaVocabulary">Their own words for the problem: one per line</label><textarea id="icaVocabulary" name="icaVocabulary">${esc4(lines(os8.ica?.vocabulary))}</textarea></div>
29414
+ <div><label for="icaHangouts">Where they read, listen and gather: one per line</label><textarea id="icaHangouts" name="icaHangouts">${esc4(lines(os8.ica?.hangouts))}</textarea></div>
29331
29415
  </div>
29332
29416
  <label for="icaVillains">What they have tried and blame: one per line</label>
29333
- <textarea id="icaVillains" name="icaVillains" placeholder="The last agency that charged $3,000 a month for two posts a week">${esc4(lines(os7.ica?.villains))}</textarea>
29417
+ <textarea id="icaVillains" name="icaVillains" placeholder="The last agency that charged $3,000 a month for two posts a week">${esc4(lines(os8.ica?.villains))}</textarea>
29334
29418
  </fieldset>
29335
29419
 
29336
29420
  <fieldset><legend>The offer: what you promise</legend>
29337
29421
  <div class="two">
29338
- <div><label for="offerName">The offer's name</label><input id="offerName" name="offerName" value="${esc4(os7.offer?.name)}" placeholder="The Full Room"></div>
29339
- <div><label for="offerPrice">Price, and what it is per</label><input id="offerPrice" name="offerPrice" value="${esc4(os7.offer?.price)}" placeholder="$5,000 per event plus 30% of revenue"></div>
29422
+ <div><label for="offerName">The offer's name</label><input id="offerName" name="offerName" value="${esc4(os8.offer?.name)}" placeholder="The Full Room"></div>
29423
+ <div><label for="offerPrice">Price, and what it is per</label><input id="offerPrice" name="offerPrice" value="${esc4(os8.offer?.price)}" placeholder="$5,000 per event plus 30% of revenue"></div>
29340
29424
  </div>
29341
29425
  <label for="offerPromise">The promise: one sentence a prospect could repeat</label>
29342
- <textarea id="offerPromise" name="offerPromise" placeholder="A full room in one weekend, and a system that keeps booking after we leave">${esc4(os7.offer?.promise)}</textarea>
29426
+ <textarea id="offerPromise" name="offerPromise" placeholder="A full room in one weekend, and a system that keeps booking after we leave">${esc4(os8.offer?.promise)}</textarea>
29343
29427
  <label for="offerStack">What is in it: one per line</label>
29344
- <textarea id="offerStack" name="offerStack" placeholder="The campaign page&#10;The AI receptionist&#10;The 30-day nurture">${esc4(lines(os7.offer?.valueStack))}</textarea>
29428
+ <textarea id="offerStack" name="offerStack" placeholder="The campaign page&#10;The AI receptionist&#10;The 30-day nurture">${esc4(lines(os8.offer?.valueStack))}</textarea>
29345
29429
  <div class="two">
29346
- <div><label for="offerGuarantee">The guarantee, exactly as you would say it</label><textarea id="offerGuarantee" name="offerGuarantee">${esc4(os7.offer?.guarantee)}</textarea></div>
29347
- <div><label for="offerAccelerators">Why now: bonuses, deadlines, scarcity, one per line</label><textarea id="offerAccelerators" name="offerAccelerators">${esc4(lines(os7.offer?.accelerators))}</textarea></div>
29430
+ <div><label for="offerGuarantee">The guarantee, exactly as you would say it</label><textarea id="offerGuarantee" name="offerGuarantee">${esc4(os8.offer?.guarantee)}</textarea></div>
29431
+ <div><label for="offerAccelerators">Why now: bonuses, deadlines, scarcity, one per line</label><textarea id="offerAccelerators" name="offerAccelerators">${esc4(lines(os8.offer?.accelerators))}</textarea></div>
29348
29432
  </div>
29349
29433
  </fieldset>
29350
29434
 
@@ -31551,7 +31635,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
31551
31635
  const systemApps = "/Applications";
31552
31636
  let base = home;
31553
31637
  try {
31554
- fs23.accessSync(systemApps, fs23.constants.W_OK);
31638
+ fs24.accessSync(systemApps, fs24.constants.W_OK);
31555
31639
  base = "";
31556
31640
  } catch {
31557
31641
  }
@@ -31628,7 +31712,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
31628
31712
  function launcherInstalled(port = 7300, platform2 = process.platform, home = os4.homedir()) {
31629
31713
  try {
31630
31714
  const plan = planLauncher(platform2, home, port, installingEntry());
31631
- return plan.files.every((f) => fs23.existsSync(f.path));
31715
+ return plan.files.every((f) => fs24.existsSync(f.path));
31632
31716
  } catch {
31633
31717
  return false;
31634
31718
  }
@@ -31639,8 +31723,8 @@ function installLauncher(argv = []) {
31639
31723
  const plan = planLauncher(process.platform, os4.homedir(), port, installingEntry());
31640
31724
  try {
31641
31725
  for (const f of plan.files) {
31642
- fs23.mkdirSync(path22.dirname(f.path), { recursive: true });
31643
- fs23.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
31726
+ fs24.mkdirSync(path22.dirname(f.path), { recursive: true });
31727
+ fs24.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
31644
31728
  }
31645
31729
  } catch (e) {
31646
31730
  process.stderr.write(`
@@ -31660,16 +31744,66 @@ function installLauncher(argv = []) {
31660
31744
  ].join("\n"));
31661
31745
  return 0;
31662
31746
  }
31663
- var fs23, os4, path22;
31747
+ var fs24, os4, path22;
31664
31748
  var init_launcher = __esm({
31665
31749
  "src/launcher.ts"() {
31666
31750
  "use strict";
31667
- fs23 = __toESM(require("fs"));
31751
+ fs24 = __toESM(require("fs"));
31668
31752
  os4 = __toESM(require("os"));
31669
31753
  path22 = __toESM(require("path"));
31670
31754
  }
31671
31755
  });
31672
31756
 
31757
+ // src/npx-cache-check.ts
31758
+ function defaultNpxCacheDir(env = process.env, platform2 = process.platform, home = os5.homedir()) {
31759
+ const configured = env.npm_config_cache?.trim();
31760
+ const cache = configured ? configured : platform2 === "win32" ? path23.join(env.LOCALAPPDATA?.trim() || path23.join(home, "AppData", "Local"), "npm-cache") : path23.join(home, ".npm");
31761
+ return path23.join(cache, "_npx");
31762
+ }
31763
+ function damagedNpxDownloads(npxDir) {
31764
+ let entries;
31765
+ try {
31766
+ entries = fs25.readdirSync(npxDir, { withFileTypes: true });
31767
+ } catch {
31768
+ return [];
31769
+ }
31770
+ const damaged = [];
31771
+ const gone = (p) => {
31772
+ try {
31773
+ fs25.statSync(p);
31774
+ return false;
31775
+ } catch (e) {
31776
+ return e.code === "ENOENT";
31777
+ }
31778
+ };
31779
+ for (const entry of entries) {
31780
+ const dir = path23.join(npxDir, entry.name);
31781
+ const pkg2 = path23.join(dir, PACKAGE_SUBDIR);
31782
+ try {
31783
+ if (!fs25.statSync(dir).isDirectory() || !fs25.statSync(pkg2).isDirectory()) continue;
31784
+ } catch {
31785
+ continue;
31786
+ }
31787
+ if (gone(path23.join(dir, "package.json")) || gone(path23.join(pkg2, "package.json"))) damaged.push(dir);
31788
+ }
31789
+ return damaged.sort();
31790
+ }
31791
+ function damagedDownloadLine(dirs) {
31792
+ if (dirs.length === 0) return null;
31793
+ const one = dirs.length === 1;
31794
+ return `${one ? "One earlier download" : "Earlier downloads"} of GHL Command on this computer ${one ? "is" : "are"} damaged, which makes Claude say "Connection closed" instead of starting it. Delete ${one ? "this folder" : "these folders"}, then start Claude again: ${dirs.join(" and ")}`;
31795
+ }
31796
+ var fs25, os5, path23, PACKAGE_SUBDIR;
31797
+ var init_npx_cache_check = __esm({
31798
+ "src/npx-cache-check.ts"() {
31799
+ "use strict";
31800
+ fs25 = __toESM(require("node:fs"));
31801
+ os5 = __toESM(require("node:os"));
31802
+ path23 = __toESM(require("node:path"));
31803
+ PACKAGE_SUBDIR = path23.join("node_modules", "@elitedcs", "ghl-mcp");
31804
+ }
31805
+ });
31806
+
31673
31807
  // src/config-installer.ts
31674
31808
  var config_installer_exports = {};
31675
31809
  __export(config_installer_exports, {
@@ -31687,44 +31821,45 @@ __export(config_installer_exports, {
31687
31821
  });
31688
31822
  function defaultIO() {
31689
31823
  return {
31824
+ npxCacheDir: defaultNpxCacheDir(),
31690
31825
  out: (l) => process.stdout.write(l + "\n"),
31691
31826
  err: (l) => process.stderr.write(l + "\n"),
31692
- rename: (from, to) => fs24.renameSync(from, to),
31827
+ rename: (from, to) => fs26.renameSync(from, to),
31693
31828
  sleep: (ms) => new Promise((r) => setTimeout(r, ms))
31694
31829
  };
31695
31830
  }
31696
31831
  function candidateConfigPaths(opts) {
31697
31832
  const platform2 = opts?.platform ?? process.platform;
31698
- const home = opts?.home ?? os5.homedir();
31833
+ const home = opts?.home ?? os6.homedir();
31699
31834
  const file2 = "claude_desktop_config.json";
31700
31835
  const candidates = [];
31701
31836
  if (platform2 === "darwin") {
31702
- candidates.push(path23.join(home, "Library", "Application Support", "Claude", file2));
31837
+ candidates.push(path24.join(home, "Library", "Application Support", "Claude", file2));
31703
31838
  } else if (platform2 === "win32") {
31704
- const appData = opts?.appData ?? process.env.APPDATA ?? path23.join(home, "AppData", "Roaming");
31705
- candidates.push(path23.join(appData, "Claude", file2));
31706
- const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local");
31707
- const packagesDir = path23.join(localAppData, "Packages");
31839
+ const appData = opts?.appData ?? process.env.APPDATA ?? path24.join(home, "AppData", "Roaming");
31840
+ candidates.push(path24.join(appData, "Claude", file2));
31841
+ const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path24.join(home, "AppData", "Local");
31842
+ const packagesDir = path24.join(localAppData, "Packages");
31708
31843
  try {
31709
- for (const entry of fs24.readdirSync(packagesDir)) {
31844
+ for (const entry of fs26.readdirSync(packagesDir)) {
31710
31845
  if (entry.startsWith("Claude_")) {
31711
- candidates.push(path23.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file2));
31846
+ candidates.push(path24.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file2));
31712
31847
  }
31713
31848
  }
31714
31849
  } catch {
31715
31850
  }
31716
31851
  } else {
31717
- candidates.push(path23.join(home, ".config", "Claude", file2));
31852
+ candidates.push(path24.join(home, ".config", "Claude", file2));
31718
31853
  }
31719
31854
  return candidates;
31720
31855
  }
31721
31856
  function resolveConfigPath(explicitPath) {
31722
31857
  if (explicitPath) {
31723
- const p = path23.resolve(explicitPath);
31724
- return { configPath: p, exists: fs24.existsSync(p) };
31858
+ const p = path24.resolve(explicitPath);
31859
+ return { configPath: p, exists: fs26.existsSync(p) };
31725
31860
  }
31726
31861
  const candidates = candidateConfigPaths();
31727
- const existing = candidates.filter((c) => fs24.existsSync(c));
31862
+ const existing = candidates.filter((c) => fs26.existsSync(c));
31728
31863
  if (existing.length > 1) {
31729
31864
  throw new InstallStop(
31730
31865
  EXIT_REFUSED,
@@ -31738,25 +31873,25 @@ function resolveConfigPath(explicitPath) {
31738
31873
  if (existing.length === 1) return { configPath: existing[0], exists: true };
31739
31874
  return { configPath: candidates[0], exists: false };
31740
31875
  }
31741
- function resolveSymlinkPolicy(configPath, explicitPath, home = os5.homedir()) {
31876
+ function resolveSymlinkPolicy(configPath, explicitPath, home = os6.homedir()) {
31742
31877
  let st;
31743
31878
  try {
31744
- st = fs24.lstatSync(configPath);
31879
+ st = fs26.lstatSync(configPath);
31745
31880
  } catch {
31746
31881
  return configPath;
31747
31882
  }
31748
31883
  if (!st.isSymbolicLink()) return configPath;
31749
31884
  let real;
31750
31885
  try {
31751
- real = fs24.realpathSync(configPath);
31886
+ real = fs26.realpathSync(configPath);
31752
31887
  } catch {
31753
31888
  throw new InstallStop(
31754
31889
  EXIT_REFUSED,
31755
31890
  `${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.`
31756
31891
  );
31757
31892
  }
31758
- const rel = path23.relative(path23.resolve(home), real);
31759
- const outsideHome = rel.startsWith("..") || path23.isAbsolute(rel);
31893
+ const rel = path24.relative(path24.resolve(home), real);
31894
+ const outsideHome = rel.startsWith("..") || path24.isAbsolute(rel);
31760
31895
  if (outsideHome && !explicitPath) {
31761
31896
  throw new InstallStop(
31762
31897
  EXIT_REFUSED,
@@ -31766,8 +31901,8 @@ function resolveSymlinkPolicy(configPath, explicitPath, home = os5.homedir()) {
31766
31901
  return real;
31767
31902
  }
31768
31903
  function readConfigBytes(configPath) {
31769
- const bytes = fs24.readFileSync(configPath);
31770
- const stat = fs24.statSync(configPath);
31904
+ const bytes = fs26.readFileSync(configPath);
31905
+ const stat = fs26.statSync(configPath);
31771
31906
  if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 254 && bytes[1] === 255)) {
31772
31907
  throw new InstallStop(
31773
31908
  EXIT_REFUSED,
@@ -31841,23 +31976,23 @@ function backupStamp(now) {
31841
31976
  }
31842
31977
  function chooseBackupPath(configPath, now = /* @__PURE__ */ new Date()) {
31843
31978
  const base = `${configPath}.bak-${backupStamp(now)}`;
31844
- if (!fs24.existsSync(base)) return base;
31979
+ if (!fs26.existsSync(base)) return base;
31845
31980
  for (let i = 2; ; i++) {
31846
31981
  const candidate = `${base}-${i}`;
31847
- if (!fs24.existsSync(candidate)) return candidate;
31982
+ if (!fs26.existsSync(candidate)) return candidate;
31848
31983
  }
31849
31984
  }
31850
31985
  function writeVerifiedBackup(configPath, originalBytes) {
31851
31986
  const backupPath = chooseBackupPath(configPath);
31852
31987
  try {
31853
- fs24.writeFileSync(backupPath, originalBytes);
31854
- const readBack = fs24.readFileSync(backupPath);
31988
+ fs26.writeFileSync(backupPath, originalBytes);
31989
+ const readBack = fs26.readFileSync(backupPath);
31855
31990
  if (!readBack.equals(originalBytes)) {
31856
31991
  throw new Error("backup read-back did not match");
31857
31992
  }
31858
31993
  } catch (e) {
31859
31994
  try {
31860
- fs24.rmSync(backupPath, { force: true });
31995
+ fs26.rmSync(backupPath, { force: true });
31861
31996
  } catch {
31862
31997
  }
31863
31998
  throw new InstallStop(
@@ -31876,9 +32011,9 @@ function baselineOf(bytes, stat) {
31876
32011
  function assertNotStale(configPath, baseline) {
31877
32012
  let ok = false;
31878
32013
  try {
31879
- const st = fs24.statSync(configPath);
32014
+ const st = fs26.statSync(configPath);
31880
32015
  if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
31881
- ok = sha256(fs24.readFileSync(configPath)) === baseline.hash;
32016
+ ok = sha256(fs26.readFileSync(configPath)) === baseline.hash;
31882
32017
  } else {
31883
32018
  ok = false;
31884
32019
  }
@@ -31893,14 +32028,14 @@ function assertNotStale(configPath, baseline) {
31893
32028
  }
31894
32029
  }
31895
32030
  async function atomicReplace(opts) {
31896
- const dir = path23.dirname(opts.configPath);
31897
- const tempPath = path23.join(dir, `.${path23.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}`);
31898
- const fd = fs24.openSync(tempPath, "w");
32031
+ const dir = path24.dirname(opts.configPath);
32032
+ const tempPath = path24.join(dir, `.${path24.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}`);
32033
+ const fd = fs26.openSync(tempPath, "w");
31899
32034
  try {
31900
- fs24.writeFileSync(fd, opts.newBytes);
31901
- fs24.fsyncSync(fd);
32035
+ fs26.writeFileSync(fd, opts.newBytes);
32036
+ fs26.fsyncSync(fd);
31902
32037
  } finally {
31903
- fs24.closeSync(fd);
32038
+ fs26.closeSync(fd);
31904
32039
  }
31905
32040
  try {
31906
32041
  for (let attempt = 0; ; attempt++) {
@@ -31925,21 +32060,27 @@ async function atomicReplace(opts) {
31925
32060
  }
31926
32061
  } catch (e) {
31927
32062
  try {
31928
- fs24.rmSync(tempPath, { force: true });
32063
+ fs26.rmSync(tempPath, { force: true });
31929
32064
  } catch {
31930
32065
  }
31931
32066
  throw e;
31932
32067
  }
31933
32068
  try {
31934
- const dirFd = fs24.openSync(dir, "r");
32069
+ const dirFd = fs26.openSync(dir, "r");
31935
32070
  try {
31936
- fs24.fsyncSync(dirFd);
32071
+ fs26.fsyncSync(dirFd);
31937
32072
  } finally {
31938
- fs24.closeSync(dirFd);
32073
+ fs26.closeSync(dirFd);
31939
32074
  }
31940
32075
  } catch {
31941
32076
  }
31942
32077
  }
32078
+ function reportDamagedDownloads(io) {
32079
+ const line3 = damagedDownloadLine(damagedNpxDownloads(io.npxCacheDir));
32080
+ if (!line3) return;
32081
+ io.out("");
32082
+ io.out(line3);
32083
+ }
31943
32084
  function successReport(io, args) {
31944
32085
  io.out("Added GHL Command to Claude Desktop.");
31945
32086
  io.out("");
@@ -31987,7 +32128,7 @@ async function runInstall(argv, ioOverride) {
31987
32128
  try {
31988
32129
  const resolved2 = resolveConfigPath(flags.path);
31989
32130
  const configPath = resolveSymlinkPolicy(resolved2.configPath, flags.path !== void 0);
31990
- const exists = fs24.existsSync(configPath);
32131
+ const exists = fs26.existsSync(configPath);
31991
32132
  let read2 = null;
31992
32133
  let outcome;
31993
32134
  if (exists) {
@@ -31999,6 +32140,7 @@ async function runInstall(argv, ioOverride) {
31999
32140
  const { merged, keptServers, action } = outcome.tier === 3 ? { merged: { mcpServers: { [SERVER_KEY]: { command: DESIRED_ENTRY.command, args: [...DESIRED_ENTRY.args] } } }, keptServers: [], action: "added" } : mergeGhlEntry(outcome.root, flags.force === true);
32000
32141
  if (action === "identical") {
32001
32142
  io.out("GHL Command is already set up in Claude Desktop \u2014 nothing to do.");
32143
+ reportDamagedDownloads(io);
32002
32144
  return EXIT_OK;
32003
32145
  }
32004
32146
  const newText = JSON.stringify(merged, null, 2) + "\n";
@@ -32014,10 +32156,11 @@ async function runInstall(argv, ioOverride) {
32014
32156
  if (exists) io.out(`Would back up: ${chooseBackupPath(configPath)}`);
32015
32157
  if (outcome.tier === 2) io.out("Would rewrite the file in a cleaner format (original kept as the backup).");
32016
32158
  if (outcome.tier === 3) io.out("Would write a fresh settings file (original kept as the backup).");
32159
+ reportDamagedDownloads(io);
32017
32160
  return EXIT_OK;
32018
32161
  }
32019
32162
  const backupPath = exists && read2 ? writeVerifiedBackup(configPath, read2.bytes) : null;
32020
- if (!exists) fs24.mkdirSync(path23.dirname(configPath), { recursive: true });
32163
+ if (!exists) fs26.mkdirSync(path24.dirname(configPath), { recursive: true });
32021
32164
  await atomicReplace({
32022
32165
  configPath,
32023
32166
  newBytes,
@@ -32025,6 +32168,7 @@ async function runInstall(argv, ioOverride) {
32025
32168
  io
32026
32169
  });
32027
32170
  successReport(io, { configPath, backupPath, keptServers, tier: outcome.tier, hadOriginal: exists });
32171
+ reportDamagedDownloads(io);
32028
32172
  return EXIT_OK;
32029
32173
  } catch (e) {
32030
32174
  if (e instanceof InstallStop) {
@@ -32035,16 +32179,17 @@ async function runInstall(argv, ioOverride) {
32035
32179
  return EXIT_ABORTED;
32036
32180
  }
32037
32181
  }
32038
- var fs24, os5, path23, import_node_crypto7, 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;
32182
+ var fs26, os6, path24, import_node_crypto7, 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;
32039
32183
  var init_config_installer = __esm({
32040
32184
  "src/config-installer.ts"() {
32041
32185
  "use strict";
32042
- fs24 = __toESM(require("node:fs"));
32043
- os5 = __toESM(require("node:os"));
32044
- path23 = __toESM(require("node:path"));
32186
+ fs26 = __toESM(require("node:fs"));
32187
+ os6 = __toESM(require("node:os"));
32188
+ path24 = __toESM(require("node:path"));
32045
32189
  import_node_crypto7 = require("node:crypto");
32046
32190
  import_node_util = require("node:util");
32047
32191
  import_json5 = __toESM(require("json5"));
32192
+ init_npx_cache_check();
32048
32193
  SERVER_KEY = "ghl";
32049
32194
  DESIRED_ENTRY = Object.freeze({
32050
32195
  command: "npx",
@@ -32074,9 +32219,9 @@ function errLine(msg3) {
32074
32219
  function preflightWritable() {
32075
32220
  try {
32076
32221
  const dir = ensureAppDataDir();
32077
- const probe = path24.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
32078
- fs25.writeFileSync(probe, "ok");
32079
- fs25.unlinkSync(probe);
32222
+ const probe = path25.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
32223
+ fs27.writeFileSync(probe, "ok");
32224
+ fs27.unlinkSync(probe);
32080
32225
  return true;
32081
32226
  } catch (error) {
32082
32227
  errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
@@ -32264,7 +32409,7 @@ async function cmdRegisterAgencyKey(argv, registry2) {
32264
32409
  return EXIT_FS;
32265
32410
  }
32266
32411
  if (!confirmSaved(registry2)) return EXIT_FS;
32267
- process.stdout.write(`Registered agency key: ${apiKey2.substring(0, 12)}... \u2192 ${tokenRegistryPath()}
32412
+ process.stdout.write(`Registered agency key (${keyHint(apiKey2)}) \u2192 ${tokenRegistryPath()}
32268
32413
  `);
32269
32414
  restartReminder();
32270
32415
  return EXIT_OK2;
@@ -32345,13 +32490,13 @@ async function runCli(subcommand, argv) {
32345
32490
  return EXIT_USAGE2;
32346
32491
  }
32347
32492
  }
32348
- var import_node_util2, fs25, path24, import_crypto2, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
32493
+ var import_node_util2, fs27, path25, import_crypto2, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
32349
32494
  var init_cli = __esm({
32350
32495
  "src/cli.ts"() {
32351
32496
  "use strict";
32352
32497
  import_node_util2 = require("node:util");
32353
- fs25 = __toESM(require("fs"));
32354
- path24 = __toESM(require("path"));
32498
+ fs27 = __toESM(require("fs"));
32499
+ path25 = __toESM(require("path"));
32355
32500
  import_crypto2 = require("crypto");
32356
32501
  init_ghl_client();
32357
32502
  init_token_registry();
@@ -32877,11 +33022,11 @@ __export(intake_overlay_exports, {
32877
33022
  writeOverlay: () => writeOverlay
32878
33023
  });
32879
33024
  function overlayPath() {
32880
- return path25.join(appDataDir(), "intake-overlay.json");
33025
+ return path26.join(appDataDir(), "intake-overlay.json");
32881
33026
  }
32882
33027
  function readOverlay() {
32883
33028
  try {
32884
- const raw = JSON.parse(fs26.readFileSync(overlayPath(), "utf8"));
33029
+ const raw = JSON.parse(fs28.readFileSync(overlayPath(), "utf8"));
32885
33030
  if (raw && typeof raw === "object") return raw;
32886
33031
  } catch {
32887
33032
  }
@@ -32889,14 +33034,14 @@ function readOverlay() {
32889
33034
  }
32890
33035
  function writeOverlay(layer) {
32891
33036
  ensureAppDataDir();
32892
- fs26.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
33037
+ fs28.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
32893
33038
  }
32894
- var fs26, path25;
33039
+ var fs28, path26;
32895
33040
  var init_intake_overlay = __esm({
32896
33041
  "src/intake-overlay.ts"() {
32897
33042
  "use strict";
32898
- fs26 = __toESM(require("fs"));
32899
- path25 = __toESM(require("path"));
33043
+ fs28 = __toESM(require("fs"));
33044
+ path26 = __toESM(require("path"));
32900
33045
  init_credentials_store();
32901
33046
  }
32902
33047
  });
@@ -34135,11 +34280,11 @@ function recoverStuckStages(state) {
34135
34280
  return { state: { ...state, clients }, recovered };
34136
34281
  }
34137
34282
  function cockpitStatePath() {
34138
- return path26.join(appDataDir(), "cockpit-state.json");
34283
+ return path27.join(appDataDir(), "cockpit-state.json");
34139
34284
  }
34140
34285
  function readCockpitState() {
34141
34286
  try {
34142
- const raw = JSON.parse(fs27.readFileSync(cockpitStatePath(), "utf8"));
34287
+ const raw = JSON.parse(fs29.readFileSync(cockpitStatePath(), "utf8"));
34143
34288
  if (raw && raw.v === 1 && raw.clients && typeof raw.clients === "object") return raw;
34144
34289
  } catch {
34145
34290
  }
@@ -34147,7 +34292,7 @@ function readCockpitState() {
34147
34292
  }
34148
34293
  function writeCockpitState(state) {
34149
34294
  ensureAppDataDir();
34150
- fs27.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
34295
+ fs29.writeFileSync(cockpitStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
34151
34296
  }
34152
34297
  function setStage(state, locationId2, stageIndex, status) {
34153
34298
  if (!Number.isInteger(stageIndex) || stageIndex < 0 || stageIndex >= STAGES2.length) throw new Error("bad stage index");
@@ -34424,7 +34569,7 @@ function notify(message) {
34424
34569
  `);
34425
34570
  try {
34426
34571
  ensureAppDataDir();
34427
- fs27.appendFileSync(path26.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
34572
+ fs29.appendFileSync(path27.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
34428
34573
  `);
34429
34574
  } catch {
34430
34575
  }
@@ -34494,20 +34639,20 @@ function withCockpitShell(page, screen, extraHead = "") {
34494
34639
  function clientDocsDir(locationId2, base = appDataDir()) {
34495
34640
  const safeLoc = String(locationId2).replace(/[^A-Za-z0-9_-]/g, "");
34496
34641
  if (!safeLoc) throw new Error("no client");
34497
- return path26.join(base, "clients", safeLoc, "documents");
34642
+ return path27.join(base, "clients", safeLoc, "documents");
34498
34643
  }
34499
34644
  function safeDocName(name) {
34500
- const cleaned = path26.basename(String(name ?? "")).replace(/[^A-Za-z0-9._ -]/g, "_").replace(/\s+/g, " ").trim();
34501
- const ext = path26.extname(cleaned).toLowerCase();
34645
+ const cleaned = path27.basename(String(name ?? "")).replace(/[^A-Za-z0-9._ -]/g, "_").replace(/\s+/g, " ").trim();
34646
+ const ext = path27.extname(cleaned).toLowerCase();
34502
34647
  if (!cleaned || cleaned.startsWith(".") || !DOC_EXTS.has(ext)) throw new Error("Only plain text (.txt, .md, .csv), .vtt and .srt files can be saved here.");
34503
34648
  const stem = cleaned.slice(0, cleaned.length - ext.length).slice(0, 120 - ext.length).trim();
34504
34649
  return `${stem || "document"}${ext}`;
34505
34650
  }
34506
34651
  function listClientDocs(locationId2, base = appDataDir()) {
34507
34652
  const dir = clientDocsDir(locationId2, base);
34508
- if (!fs27.existsSync(dir)) return [];
34509
- return fs27.readdirSync(dir).filter((f) => DOC_EXTS.has(path26.extname(f).toLowerCase())).map((f) => {
34510
- const st = fs27.statSync(path26.join(dir, f));
34653
+ if (!fs29.existsSync(dir)) return [];
34654
+ return fs29.readdirSync(dir).filter((f) => DOC_EXTS.has(path27.extname(f).toLowerCase())).map((f) => {
34655
+ const st = fs29.statSync(path27.join(dir, f));
34511
34656
  return { name: f, bytes: st.size, savedAt: st.mtime.toISOString() };
34512
34657
  }).sort((a, b) => b.savedAt.localeCompare(a.savedAt));
34513
34658
  }
@@ -34525,7 +34670,7 @@ function readFailureText(e) {
34525
34670
  function logQuietly(message) {
34526
34671
  try {
34527
34672
  ensureAppDataDir();
34528
- fs27.appendFileSync(path26.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
34673
+ fs29.appendFileSync(path27.join(appDataDir(), "cockpit.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
34529
34674
  `);
34530
34675
  } catch {
34531
34676
  }
@@ -34598,40 +34743,40 @@ function newClientFailure(error, surface = "command-os", account2) {
34598
34743
  }
34599
34744
  function insideDir(dir, p) {
34600
34745
  try {
34601
- const real = fs27.realpathSync(p);
34602
- const base = fs27.realpathSync(dir);
34603
- return real === base || real.startsWith(base + path26.sep);
34746
+ const real = fs29.realpathSync(p);
34747
+ const base = fs29.realpathSync(dir);
34748
+ return real === base || real.startsWith(base + path27.sep);
34604
34749
  } catch {
34605
34750
  return false;
34606
34751
  }
34607
34752
  }
34608
34753
  function uniqueDocName(dir, name) {
34609
- const ext = path26.extname(name), stem = name.slice(0, name.length - ext.length);
34754
+ const ext = path27.extname(name), stem = name.slice(0, name.length - ext.length);
34610
34755
  let candidate = name;
34611
- for (let n = 2; fs27.existsSync(path26.join(dir, candidate)); n++) candidate = `${stem} (${n})${ext}`;
34756
+ for (let n = 2; fs29.existsSync(path27.join(dir, candidate)); n++) candidate = `${stem} (${n})${ext}`;
34612
34757
  return candidate;
34613
34758
  }
34614
34759
  function writeDocAtomically(dir, name, bytes) {
34615
- fs27.mkdirSync(dir, { recursive: true, mode: 448 });
34760
+ fs29.mkdirSync(dir, { recursive: true, mode: 448 });
34616
34761
  const finalName = uniqueDocName(dir, name);
34617
- const tmp = path26.join(dir, `.${finalName}.${process.pid}.tmp`);
34618
- const fd = fs27.openSync(tmp, fs27.constants.O_WRONLY | fs27.constants.O_CREAT | fs27.constants.O_EXCL | (fs27.constants.O_NOFOLLOW ?? 0), 384);
34762
+ const tmp = path27.join(dir, `.${finalName}.${process.pid}.tmp`);
34763
+ const fd = fs29.openSync(tmp, fs29.constants.O_WRONLY | fs29.constants.O_CREAT | fs29.constants.O_EXCL | (fs29.constants.O_NOFOLLOW ?? 0), 384);
34619
34764
  try {
34620
- fs27.writeSync(fd, bytes);
34621
- fs27.fsyncSync(fd);
34765
+ fs29.writeSync(fd, bytes);
34766
+ fs29.fsyncSync(fd);
34622
34767
  } finally {
34623
- fs27.closeSync(fd);
34768
+ fs29.closeSync(fd);
34624
34769
  }
34625
- const dest = path26.join(dir, finalName);
34770
+ const dest = path27.join(dir, finalName);
34626
34771
  try {
34627
- if (fs27.lstatSync(dest).isSymbolicLink()) {
34628
- fs27.unlinkSync(tmp);
34772
+ if (fs29.lstatSync(dest).isSymbolicLink()) {
34773
+ fs29.unlinkSync(tmp);
34629
34774
  throw new Error("that name is taken by a link");
34630
34775
  }
34631
34776
  } catch (e) {
34632
34777
  if (e.code !== "ENOENT") throw e;
34633
34778
  }
34634
- fs27.renameSync(tmp, dest);
34779
+ fs29.renameSync(tmp, dest);
34635
34780
  return finalName;
34636
34781
  }
34637
34782
  function readableAccount(locationId2, roster = currentRosterRef(), state = readCockpitState()) {
@@ -35880,8 +36025,23 @@ function cockpitHeader(req, name) {
35880
36025
  const value = req.headers[name];
35881
36026
  return Array.isArray(value) ? value.join(",") : value ?? "";
35882
36027
  }
36028
+ function headerFields(req, name) {
36029
+ if (req instanceof http.IncomingMessage || "rawHeaders" in req) {
36030
+ const raw = req.rawHeaders;
36031
+ if (!Array.isArray(raw)) return [];
36032
+ const values = [];
36033
+ for (let i = 0; i + 1 < raw.length; i += 2) {
36034
+ if (typeof raw[i] === "string" && raw[i].toLowerCase() === name) values.push(String(raw[i + 1]));
36035
+ }
36036
+ return values;
36037
+ }
36038
+ const value = req.headers[name];
36039
+ return value === void 0 ? [] : Array.isArray(value) ? value : [value];
36040
+ }
35883
36041
  function isAllowedCockpitHost(req, port) {
35884
- const host = cockpitHeader(req, "host").toLowerCase();
36042
+ const hosts = headerFields(req, "host");
36043
+ if (hosts.length !== 1) return false;
36044
+ const host = hosts[0].toLowerCase();
35885
36045
  return host === `127.0.0.1:${port}` || host === `localhost:${port}`;
35886
36046
  }
35887
36047
  function mayServeSeat(req, port) {
@@ -35898,8 +36058,10 @@ function checkCockpitPath(req) {
35898
36058
  return null;
35899
36059
  }
35900
36060
  function checkCockpitOrigin(req, port) {
35901
- const origin = cockpitHeader(req, "origin").toLowerCase();
35902
- if (req.headers.origin !== void 0 && origin !== `http://127.0.0.1:${port}` && origin !== `http://localhost:${port}`) {
36061
+ const origins = headerFields(req, "origin");
36062
+ if (origins.length === 0) return null;
36063
+ const origin = origins.length === 1 ? origins[0].toLowerCase() : "";
36064
+ if (origin !== `http://127.0.0.1:${port}` && origin !== `http://localhost:${port}`) {
35903
36065
  return { status: 403, error: "cross-origin request rejected" };
35904
36066
  }
35905
36067
  return null;
@@ -35924,7 +36086,8 @@ function cockpitGate(req, seatToken, port) {
35924
36086
  if (denied) return denied;
35925
36087
  const pathname = cockpitPath(req);
35926
36088
  if (SEAT_GUARDED_READS.includes(pathname) || pathname === "/api/design" || pathname.startsWith("/api/design/")) {
35927
- if (cockpitHeader(req, "sec-fetch-site").toLowerCase() === "cross-site") {
36089
+ const sites = headerFields(req, "sec-fetch-site").map((v) => v.trim().toLowerCase());
36090
+ if (sites.length > 1 || sites.length === 1 && sites[0] !== "same-origin" && sites[0] !== "none") {
35928
36091
  return { status: 403, error: "cross-site request rejected" };
35929
36092
  }
35930
36093
  return checkCockpitOrigin(req, port) ?? checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
@@ -35961,7 +36124,7 @@ async function runDashboard(argv) {
35961
36124
  if (await cockpitAlreadyRunning(port)) {
35962
36125
  openBrowser(port);
35963
36126
  process.stderr.write(`
35964
- Command OS is already running. Opening http://localhost:${port}
36127
+ Command OS is already running. Opening localhost:${port}
35965
36128
 
35966
36129
  `);
35967
36130
  return 0;
@@ -36266,8 +36429,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36266
36429
  if (!bytes.length) throw new Error("that file is empty");
36267
36430
  if (bytes.length > DOC_MAX_BYTES) throw new Error("that file is over 20 MB");
36268
36431
  const dir = clientDocsDir(args.locationId);
36269
- fs27.mkdirSync(dir, { recursive: true, mode: 448 });
36270
- if (!insideDir(path26.join(appDataDir(), "clients"), dir)) throw new Error("This client's documents folder is not where it should be, so nothing was saved.");
36432
+ fs29.mkdirSync(dir, { recursive: true, mode: 448 });
36433
+ if (!insideDir(path27.join(appDataDir(), "clients"), dir)) throw new Error("This client's documents folder is not where it should be, so nothing was saved.");
36271
36434
  const saved = writeDocAtomically(dir, name, bytes);
36272
36435
  res.end(JSON.stringify({ ok: true, name: saved, files: listClientDocs(args.locationId) }));
36273
36436
  } catch (e) {
@@ -36614,10 +36777,10 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36614
36777
  if (args.file) {
36615
36778
  const name = safeDocName(args.file);
36616
36779
  const dir = clientDocsDir(args.locationId);
36617
- const p = path26.join(dir, name);
36618
- if (!fs27.existsSync(p)) throw new Error("that document is not saved for this client");
36619
- if (fs27.lstatSync(p).isSymbolicLink() || !insideDir(dir, p)) throw new Error("that document is not saved for this client");
36620
- const text = fs27.readFileSync(p, "utf8");
36780
+ const p = path27.join(dir, name);
36781
+ if (!fs29.existsSync(p)) throw new Error("that document is not saved for this client");
36782
+ if (fs29.lstatSync(p).isSymbolicLink() || !insideDir(dir, p)) throw new Error("that document is not saved for this client");
36783
+ const text = fs29.readFileSync(p, "utf8");
36621
36784
  if (text.length > 6e4) throw new Error("The limit is 60,000 characters. Paste an excerpt or shorten this text file.");
36622
36785
  fileSource = { kind: "text", text };
36623
36786
  }
@@ -36625,7 +36788,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36625
36788
  let recorderPatterns = [];
36626
36789
  if (source.kind === "recorder") {
36627
36790
  recorderPatterns = await new Promise((resolve12) => {
36628
- const ls = (0, import_child_process5.spawn)(claudeBin(), ["mcp", "list"], { stdio: ["ignore", "pipe", "ignore"] });
36791
+ const mcpList = claudeCommand(claudeBin(), ["mcp", "list"]);
36792
+ const ls = (0, import_child_process5.spawn)(mcpList.command, mcpList.args, { stdio: ["ignore", "pipe", "ignore"] });
36629
36793
  let buf = "";
36630
36794
  ls.stdout.on("data", (d) => {
36631
36795
  buf += d.toString();
@@ -36690,14 +36854,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36690
36854
  }
36691
36855
  if (req.method === "GET" && /^\/brand\/[a-z0-9-]+\/screenshot\.png$/.test(url)) {
36692
36856
  const slug3 = url.split("/")[2];
36693
- const file2 = path26.join(brandDir(slug3), "screenshot.png");
36694
- if (!fs27.existsSync(file2)) {
36857
+ const file2 = path27.join(brandDir(slug3), "screenshot.png");
36858
+ if (!fs29.existsSync(file2)) {
36695
36859
  res.writeHead(404);
36696
36860
  res.end();
36697
36861
  return;
36698
36862
  }
36699
36863
  res.writeHead(200, { "Content-Type": "image/png", "Cache-Control": "no-store" });
36700
- res.end(fs27.readFileSync(file2));
36864
+ res.end(fs29.readFileSync(file2));
36701
36865
  return;
36702
36866
  }
36703
36867
  if (req.method === "GET" && /^\/brand\/[a-z0-9-]+$/.test(url)) {
@@ -36706,7 +36870,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36706
36870
  const dna = houseLooks().find((d) => d.slug === slug3) ?? readBrand(slug3);
36707
36871
  const dir = brandExists(slug3) ? brandDir(slug3) : void 0;
36708
36872
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
36709
- res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
36873
+ res.end(brandCardHtml(dna, { screenshot: dir ? fs29.existsSync(path27.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
36710
36874
  } catch (e) {
36711
36875
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
36712
36876
  res.end(e.message);
@@ -36868,7 +37032,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
36868
37032
  }
36869
37033
  try {
36870
37034
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
36871
- res.end(fs27.readFileSync(localCopyPath(locationId2), "utf8"));
37035
+ res.end(fs29.readFileSync(localCopyPath(locationId2), "utf8"));
36872
37036
  } catch {
36873
37037
  res.end("The handoff is not prepared yet. Run Verify first.");
36874
37038
  }
@@ -37434,7 +37598,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
37434
37598
  }
37435
37599
  const roster = currentRoster();
37436
37600
  process.stderr.write(`
37437
- Command OS cockpit running: http://localhost:${port}
37601
+ Command OS cockpit running: localhost:${port}
37438
37602
  ${roster.length} client${roster.length === 1 ? "" : "s"} on the board. Ctrl-C to stop.
37439
37603
 
37440
37604
  `);
@@ -37457,7 +37621,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
37457
37621
  process.on("SIGTERM", stop);
37458
37622
  });
37459
37623
  }
37460
- var import_node_util4, fs27, path26, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS;
37624
+ var import_node_util4, fs29, path27, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS;
37461
37625
  var init_dashboard = __esm({
37462
37626
  "src/dashboard.ts"() {
37463
37627
  "use strict";
@@ -37476,8 +37640,8 @@ var init_dashboard = __esm({
37476
37640
  init_handoff_snapshot();
37477
37641
  init_member_progress();
37478
37642
  init_build_obligations();
37479
- fs27 = __toESM(require("fs"));
37480
- path26 = __toESM(require("path"));
37643
+ fs29 = __toESM(require("fs"));
37644
+ path27 = __toESM(require("path"));
37481
37645
  http = __toESM(require("http"));
37482
37646
  import_child_process4 = require("child_process");
37483
37647
  init_credentials_store();
@@ -37525,6 +37689,7 @@ var init_dashboard = __esm({
37525
37689
  init_blueprint_review();
37526
37690
  init_intake_overlay();
37527
37691
  init_stage_runner();
37692
+ init_claude_locate();
37528
37693
  import_child_process5 = require("child_process");
37529
37694
  init_ghl_client();
37530
37695
  init_client_capabilities();
@@ -37677,7 +37842,7 @@ GHL Command version: v${mcpVersion}` : "";
37677
37842
  type: "text",
37678
37843
  text: `Current location: ${loc?.name || "Unknown"}
37679
37844
  ID: ${client.defaultLocationId}
37680
- API Key: ${client.getApiKeyPrefix()}
37845
+ API key: ${client.getApiKeyHint()}
37681
37846
  Address: ${loc?.address || "N/A"}
37682
37847
  Email: ${loc?.email || "N/A"}
37683
37848
  Token registry: ${registeredCount} location(s) registered${versionLine}`
@@ -37703,7 +37868,7 @@ Token registry: ${registeredCount} location(s) registered${versionLine}`
37703
37868
  },
37704
37869
  async ({ locationId: locationId2 }) => withSwitchLock(async () => {
37705
37870
  const previousId = client.defaultLocationId;
37706
- const previousKeyPrefix = client.getApiKeyPrefix();
37871
+ const previousKeyHint = client.getApiKeyHint();
37707
37872
  const previousApiKey = client.getApiKey();
37708
37873
  const previousBuilderApiKey = builderClient?.getApiKey();
37709
37874
  try {
@@ -37736,7 +37901,7 @@ Token registry: ${registeredCount} location(s) registered${versionLine}`
37736
37901
  const protectedLine = protectedName ? `
37737
37902
 
37738
37903
  \u{1F512} ${protectedName} is on this machine's protected list. You can read and report on it; nothing here can be created, changed or deleted, and no approval overrides that.` : "";
37739
- const keyStatus = keySwapped ? `API key swapped: ${previousKeyPrefix} \u2192 ${client.getApiKeyPrefix()}` : `API key unchanged (${client.getApiKeyPrefix()})${!registry2?.getToken(locationId2) ? " \u2014 consider using register_location to add this location's key" : ""}`;
37904
+ const keyStatus = keySwapped ? `API key swapped: ${previousKeyHint} \u2192 ${client.getApiKeyHint()}` : `API key unchanged (${client.getApiKeyHint()})${!registry2?.getToken(locationId2) ? " \u2014 consider using register_location to add this location's key" : ""}`;
37740
37905
  return {
37741
37906
  content: [
37742
37907
  {
@@ -37840,7 +38005,7 @@ Make sure:
37840
38005
  content: [
37841
38006
  {
37842
38007
  type: "text",
37843
- text: `Agency key ${hadKey ? "replaced" : "registered"}: ${apiKey2.substring(0, 12)}...
38008
+ text: `Agency key ${hadKey ? "replaced" : "registered"} (${keyHint(apiKey2)}).
37844
38009
  Saved to the token registry.
37845
38010
 
37846
38011
  Agency-wide tools now available: list_snapshots, create_snapshot_share_link, and list_available_locations (across all sub-accounts).`
@@ -38130,7 +38295,7 @@ ${lines.join("\n")}
38130
38295
  }
38131
38296
  return { content: [{
38132
38297
  type: "text",
38133
- text: `Created ${args.name} (${outcome.locationId}). ` + (outcome.snapshotId ? `Snapshot ${outcome.snapshotId} is loading. GoHighLevel applies its assets over the next minute or two. ` : "No snapshot loaded. ") + (outcome.registered ? "Continue with its intake in GHL Command. " : "In this sub-account, create a Private Integration key in Settings. Register it in GHL Command with its key. ") + `Open http://localhost:7300/new#client=${encodeURIComponent(outcome.locationId)}.`
38298
+ text: `Created ${args.name} (${outcome.locationId}). ` + (outcome.snapshotId ? `Snapshot ${outcome.snapshotId} is loading. GoHighLevel applies its assets over the next minute or two. ` : "No snapshot loaded. ") + (outcome.registered ? "Continue with its intake in GHL Command. " : "In this sub-account, create a Private Integration key in Settings. Register it in GHL Command with its key. ") + `Open localhost:7300/new#client=${encodeURIComponent(outcome.locationId)}.`
38134
38299
  }] };
38135
38300
  }
38136
38301
  );
@@ -39430,13 +39595,13 @@ function buildContactSmartListBody(args) {
39430
39595
  function registerSmartListTools(server2, builderClient) {
39431
39596
  if (!builderClient) return;
39432
39597
  const client = builderClient;
39433
- async function smartListRequest(method, path39, body) {
39434
- const url = `${SMARTLIST_BASE}${path39}`;
39598
+ async function smartListRequest(method, path40, body) {
39599
+ const url = `${SMARTLIST_BASE}${path40}`;
39435
39600
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
39436
39601
  const response = await builderRequest(client, method, url, sendsBody ? { body } : {});
39437
39602
  if (!response.ok) {
39438
39603
  const text2 = await response.text();
39439
- throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path39}
39604
+ throw new Error(`Smart Lists API Error ${response.status}: ${method} ${path40}
39440
39605
  ${text2}`);
39441
39606
  }
39442
39607
  const text = await response.text();
@@ -39665,11 +39830,11 @@ var init_smart_lists = __esm({
39665
39830
  function registerReputationTools(server2, builderClient) {
39666
39831
  if (!builderClient) return;
39667
39832
  const client = builderClient;
39668
- async function reputationRequest(method, path39) {
39669
- const response = await builderRequest(client, method, `${REPUTATION_BASE}${path39}`);
39833
+ async function reputationRequest(method, path40) {
39834
+ const response = await builderRequest(client, method, `${REPUTATION_BASE}${path40}`);
39670
39835
  if (!response.ok) {
39671
39836
  const text2 = await response.text();
39672
- throw new Error(`Reputation API Error ${response.status}: ${method} ${path39}
39837
+ throw new Error(`Reputation API Error ${response.status}: ${method} ${path40}
39673
39838
  ${text2}`);
39674
39839
  }
39675
39840
  const text = await response.text();
@@ -39807,11 +39972,11 @@ var init_email_campaigns = __esm({
39807
39972
  function registerMembershipTools(server2, builderClient) {
39808
39973
  if (!builderClient) return;
39809
39974
  const client = builderClient;
39810
- async function membershipRequest(path39, method = "GET", body) {
39811
- const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path39}`, body ? { body } : {});
39975
+ async function membershipRequest(path40, method = "GET", body) {
39976
+ const response = await builderRequest(client, method, `${MEMBERSHIP_BASE2}${path40}`, body ? { body } : {});
39812
39977
  if (!response.ok) {
39813
39978
  const text2 = await response.text();
39814
- throw new Error(`Membership API Error ${response.status}: ${method} ${path39}
39979
+ throw new Error(`Membership API Error ${response.status}: ${method} ${path40}
39815
39980
  ${text2}`);
39816
39981
  }
39817
39982
  const text = await response.text();
@@ -40004,21 +40169,21 @@ function isAlreadyExistsError(message) {
40004
40169
  }
40005
40170
  function registerTemplateDeployerTools(server2, client) {
40006
40171
  const builderClient = WorkflowBuilderClient.fromEnv();
40007
- const templatesDir = path27.resolve(__dirname, "..", "templates");
40172
+ const templatesDir = path28.resolve(__dirname, "..", "templates");
40008
40173
  function validateTemplatePath(templateFile) {
40009
- const resolved2 = path27.resolve(templateFile);
40010
- const realPath = fs28.existsSync(resolved2) ? fs28.realpathSync(resolved2) : resolved2;
40174
+ const resolved2 = path28.resolve(templateFile);
40175
+ const realPath = fs30.existsSync(resolved2) ? fs30.realpathSync(resolved2) : resolved2;
40011
40176
  const allowedDirs = [
40012
40177
  templatesDir,
40013
- path27.resolve(process.cwd(), "templates"),
40014
- path27.resolve(__dirname, "..", "..", "templates")
40015
- ].map((d) => fs28.existsSync(d) ? fs28.realpathSync(d) : d);
40016
- const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path27.sep));
40178
+ path28.resolve(process.cwd(), "templates"),
40179
+ path28.resolve(__dirname, "..", "..", "templates")
40180
+ ].map((d) => fs30.existsSync(d) ? fs30.realpathSync(d) : d);
40181
+ const isAllowed = allowedDirs.some((dir) => realPath.startsWith(dir + path28.sep));
40017
40182
  if (!isAllowed) {
40018
40183
  throw new Error(`Template file must be inside a templates/ directory. Got: ${realPath}`);
40019
40184
  }
40020
- if (path27.relative(allowedDirs[0], realPath).startsWith("..")) {
40021
- const otherMatch = allowedDirs.slice(1).some((d) => !path27.relative(d, realPath).startsWith(".."));
40185
+ if (path28.relative(allowedDirs[0], realPath).startsWith("..")) {
40186
+ const otherMatch = allowedDirs.slice(1).some((d) => !path28.relative(d, realPath).startsWith(".."));
40022
40187
  if (!otherMatch) {
40023
40188
  throw new Error(`Template path escapes allowed directory.`);
40024
40189
  }
@@ -40033,26 +40198,26 @@ function registerTemplateDeployerTools(server2, client) {
40033
40198
  try {
40034
40199
  const dirs = [
40035
40200
  templatesDir,
40036
- path27.resolve(process.cwd(), "templates"),
40037
- path27.resolve(__dirname, "..", "..", "templates")
40201
+ path28.resolve(process.cwd(), "templates"),
40202
+ path28.resolve(__dirname, "..", "..", "templates")
40038
40203
  ];
40039
40204
  const templates = [];
40040
40205
  const seen = /* @__PURE__ */ new Set();
40041
40206
  for (const dir of dirs) {
40042
- if (!fs28.existsSync(dir)) continue;
40043
- const files = fs28.readdirSync(dir).filter((f) => f.endsWith(".json"));
40207
+ if (!fs30.existsSync(dir)) continue;
40208
+ const files = fs30.readdirSync(dir).filter((f) => f.endsWith(".json"));
40044
40209
  for (const file2 of files) {
40045
- const full = path27.join(dir, file2);
40210
+ const full = path28.join(dir, file2);
40046
40211
  let real;
40047
40212
  try {
40048
- real = fs28.realpathSync(full);
40213
+ real = fs30.realpathSync(full);
40049
40214
  } catch {
40050
40215
  continue;
40051
40216
  }
40052
40217
  if (seen.has(real)) continue;
40053
40218
  seen.add(real);
40054
40219
  try {
40055
- const content = JSON.parse(fs28.readFileSync(full, "utf-8"));
40220
+ const content = JSON.parse(fs30.readFileSync(full, "utf-8"));
40056
40221
  if (typeof content.templateName !== "string" || !content.templateName) continue;
40057
40222
  const parsed2 = TemplateSchema.safeParse(content);
40058
40223
  templates.push({
@@ -40089,7 +40254,7 @@ function registerTemplateDeployerTools(server2, client) {
40089
40254
  async ({ templateFile }) => {
40090
40255
  try {
40091
40256
  const safePath = validateTemplatePath(templateFile);
40092
- const content = JSON.parse(fs28.readFileSync(safePath, "utf-8"));
40257
+ const content = JSON.parse(fs30.readFileSync(safePath, "utf-8"));
40093
40258
  return {
40094
40259
  content: [
40095
40260
  {
@@ -40129,7 +40294,7 @@ function registerTemplateDeployerTools(server2, client) {
40129
40294
  try {
40130
40295
  const locId = client.resolveLocationId(locationId2);
40131
40296
  const safePath = validateTemplatePath(templateFile);
40132
- const template = TemplateSchema.parse(JSON.parse(fs28.readFileSync(safePath, "utf-8")));
40297
+ const template = TemplateSchema.parse(JSON.parse(fs30.readFileSync(safePath, "utf-8")));
40133
40298
  const resolve12 = (text) => {
40134
40299
  if (typeof text !== "string") return text;
40135
40300
  let result = text;
@@ -40501,15 +40666,15 @@ ${errors.join("\n")}` : dryRun ? "\nNo errors!" : "\nNo errors \u2014 every item
40501
40666
  }
40502
40667
  );
40503
40668
  }
40504
- var import_zod62, fs28, path27, TemplateSchema;
40669
+ var import_zod62, fs30, path28, TemplateSchema;
40505
40670
  var init_template_deployer = __esm({
40506
40671
  "src/tools/template-deployer.ts"() {
40507
40672
  "use strict";
40508
40673
  import_zod62 = require("zod");
40509
40674
  init_workflow_builder_client();
40510
40675
  init_tool_helpers();
40511
- fs28 = __toESM(require("fs"));
40512
- path27 = __toESM(require("path"));
40676
+ fs30 = __toESM(require("fs"));
40677
+ path28 = __toESM(require("path"));
40513
40678
  init_workflow_builder_client();
40514
40679
  TemplateSchema = import_zod62.z.object({
40515
40680
  templateName: import_zod62.z.string(),
@@ -40607,15 +40772,15 @@ async function collectInstallChecks(installedVersion, client, builderClient, reg
40607
40772
  signal: AbortSignal.timeout(8e3)
40608
40773
  });
40609
40774
  if (response.status === 401) {
40610
- return { name: "GHL API key", status: "fail", detail: `401 Unauthorized \u2014 key ${client.getApiKeyPrefix()} is invalid or revoked. Run setup_ghl_mcp with a fresh key.` };
40775
+ return { name: "GHL API key", status: "fail", detail: `401 Unauthorized \u2014 the key ${client.getApiKeyHint()} is invalid or revoked. Run setup_ghl_mcp with a fresh key.` };
40611
40776
  }
40612
40777
  if (response.status === 403) {
40613
- return { name: "GHL API key", status: "fail", detail: `403 Forbidden \u2014 key ${client.getApiKeyPrefix()} doesn't have access to location ${locId}. The key may belong to a different sub-account.` };
40778
+ return { name: "GHL API key", status: "fail", detail: `403 Forbidden \u2014 the key ${client.getApiKeyHint()} doesn't have access to location ${locId}. The key may belong to a different sub-account.` };
40614
40779
  }
40615
40780
  if (!response.ok) {
40616
40781
  return { name: "GHL API key", status: "warn", detail: `HTTP ${response.status} reading /locations/${locId} \u2014 key may be limited or GHL is having issues.` };
40617
40782
  }
40618
- return { name: "GHL API key", status: "pass", detail: `Valid (key ${client.getApiKeyPrefix()}).` };
40783
+ return { name: "GHL API key", status: "pass", detail: `Valid (key ${client.getApiKeyHint()}).` };
40619
40784
  } catch (error) {
40620
40785
  return { name: "GHL API key", status: "warn", detail: `Network error: ${error instanceof Error ? error.message : String(error)}. Server starts anyway; tool calls will report errors when invoked.` };
40621
40786
  }
@@ -40830,10 +40995,10 @@ async function confirm(run) {
40830
40995
  async function runProbes(client, loc) {
40831
40996
  return Promise.all(
40832
40997
  PROBES.map((probe) => confirm(async () => {
40833
- const path39 = probe.path.replace("{loc}", loc);
40998
+ const path40 = probe.path.replace("{loc}", loc);
40834
40999
  const params = probe.params ? Object.fromEntries(Object.entries(probe.params).map(([k, v]) => [k, substitute(v, loc)])) : void 0;
40835
41000
  try {
40836
- const raw = await client.get(path39, { params, noRetry: true });
41001
+ const raw = await client.get(path40, { params, noRetry: true });
40837
41002
  const count2 = countRows(raw, probe.listKeys);
40838
41003
  return {
40839
41004
  key: probe.key,
@@ -41106,14 +41271,14 @@ var init_checkup = __esm({
41106
41271
 
41107
41272
  // src/tools/user-guide.ts
41108
41273
  function resolveGuidePath() {
41109
- const bundled = path28.resolve(__dirname, "..", "guide", "guide.html");
41110
- if (fs29.existsSync(bundled)) return bundled;
41111
- const devRoot = path28.resolve(__dirname, "..", "..");
41274
+ const bundled = path29.resolve(__dirname, "..", "guide", "guide.html");
41275
+ if (fs31.existsSync(bundled)) return bundled;
41276
+ const devRoot = path29.resolve(__dirname, "..", "..");
41112
41277
  try {
41113
- const pkg2 = JSON.parse(fs29.readFileSync(path28.join(devRoot, "package.json"), "utf8"));
41278
+ const pkg2 = JSON.parse(fs31.readFileSync(path29.join(devRoot, "package.json"), "utf8"));
41114
41279
  if (pkg2.name === "@elitedcs/ghl-mcp") {
41115
- const dev = path28.join(devRoot, "guide", "guide.html");
41116
- if (fs29.existsSync(dev)) return dev;
41280
+ const dev = path29.join(devRoot, "guide", "guide.html");
41281
+ if (fs31.existsSync(dev)) return dev;
41117
41282
  }
41118
41283
  } catch {
41119
41284
  }
@@ -41122,7 +41287,7 @@ function resolveGuidePath() {
41122
41287
  function guideContents(file2) {
41123
41288
  let html2 = "";
41124
41289
  try {
41125
- html2 = fs29.readFileSync(file2, "utf8");
41290
+ html2 = fs31.readFileSync(file2, "utf8");
41126
41291
  } catch {
41127
41292
  return "The guide is installed but could not be read.";
41128
41293
  }
@@ -41146,11 +41311,11 @@ function registerUserGuideTools(server2) {
41146
41311
  { open: import_zod64.z.boolean().optional().describe("Open the guide in the browser. Takes over the person's screen \u2014 only when they asked for it.") },
41147
41312
  async ({ open }) => {
41148
41313
  const guidePath = resolveGuidePath();
41149
- if (!fs29.existsSync(guidePath)) {
41314
+ if (!fs31.existsSync(guidePath)) {
41150
41315
  return {
41151
41316
  content: [{
41152
41317
  type: "text",
41153
- text: `The bundled user guide is missing (expected at ${guidePath}). Installed copies always include it \u2014 a reinstall restores it: npx -y @elitedcs/ghl-mcp cli install. In a development checkout, render it with: node scripts/build-guides.mjs`
41318
+ text: `The bundled user guide is missing (expected at ${guidePath}). Installed copies always include it \u2014 a reinstall restores it: npx -y @elitedcs/ghl-mcp@latest cli install. In a development checkout, render it with: node scripts/build-guides.mjs`
41154
41319
  }]
41155
41320
  };
41156
41321
  }
@@ -41179,14 +41344,14 @@ ${guidePath}`;
41179
41344
  }
41180
41345
  );
41181
41346
  }
41182
- var import_child_process6, import_zod64, fs29, path28;
41347
+ var import_child_process6, import_zod64, fs31, path29;
41183
41348
  var init_user_guide = __esm({
41184
41349
  "src/tools/user-guide.ts"() {
41185
41350
  "use strict";
41186
41351
  import_child_process6 = require("child_process");
41187
41352
  import_zod64 = require("zod");
41188
- fs29 = __toESM(require("fs"));
41189
- path28 = __toESM(require("path"));
41353
+ fs31 = __toESM(require("fs"));
41354
+ path29 = __toESM(require("path"));
41190
41355
  }
41191
41356
  });
41192
41357
 
@@ -43389,9 +43554,9 @@ async function settle(page) {
43389
43554
  }
43390
43555
  async function readTarget(target) {
43391
43556
  if (isUrl(target)) return { html: null, url: target };
43392
- const abs = path29.resolve(target);
43393
- if (!fs30.existsSync(abs)) throw new Error(`No file at ${abs}.`);
43394
- return { html: fs30.readFileSync(abs, "utf8"), url: (0, import_node_url.pathToFileURL)(abs).toString() };
43557
+ const abs = path30.resolve(target);
43558
+ if (!fs32.existsSync(abs)) throw new Error(`No file at ${abs}.`);
43559
+ return { html: fs32.readFileSync(abs, "utf8"), url: (0, import_node_url.pathToFileURL)(abs).toString() };
43395
43560
  }
43396
43561
  function slidePixels(index, png, file2, sec, brand) {
43397
43562
  let img;
@@ -43439,7 +43604,7 @@ async function verifyDesign(opts) {
43439
43604
  const { html: html2, url } = await readTarget(opts.target);
43440
43605
  const placement = opts.placement ?? "html";
43441
43606
  const medium = opts.medium;
43442
- fs30.mkdirSync(opts.outDir, { recursive: true });
43607
+ fs32.mkdirSync(opts.outDir, { recursive: true });
43443
43608
  const files = [];
43444
43609
  const screenshots = [];
43445
43610
  const at = (/* @__PURE__ */ new Date()).toISOString();
@@ -43484,8 +43649,8 @@ async function verifyDesign(opts) {
43484
43649
  try {
43485
43650
  await loc.scrollIntoViewIfNeeded({ timeout: 5e3 });
43486
43651
  const png = await loc.screenshot({ type: "png", timeout: 15e3 });
43487
- fs30.writeFileSync(path29.join(opts.outDir, file2), png);
43488
- files.push(path29.join(opts.outDir, file2));
43652
+ fs32.writeFileSync(path30.join(opts.outDir, file2), png);
43653
+ files.push(path30.join(opts.outDir, file2));
43489
43654
  screenshots.push(file2);
43490
43655
  pixels.push(slidePixels(sec.index, png, file2, sec, opts.brand));
43491
43656
  } catch {
@@ -43517,8 +43682,8 @@ async function verifyDesign(opts) {
43517
43682
  const shot = "page-1440.png";
43518
43683
  try {
43519
43684
  const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
43520
- fs30.writeFileSync(path29.join(opts.outDir, shot), png);
43521
- files.push(path29.join(opts.outDir, shot));
43685
+ fs32.writeFileSync(path30.join(opts.outDir, shot), png);
43686
+ files.push(path30.join(opts.outDir, shot));
43522
43687
  screenshots.push(shot);
43523
43688
  } catch {
43524
43689
  }
@@ -43526,8 +43691,8 @@ async function verifyDesign(opts) {
43526
43691
  await page.setViewportSize({ width: 390, height: 844 });
43527
43692
  await page.evaluate(SETTLE_SCROLL_JS).catch(() => void 0);
43528
43693
  const png = await page.screenshot({ type: "png", fullPage: true, timeout: 2e4 });
43529
- fs30.writeFileSync(path29.join(opts.outDir, "page-390.png"), png);
43530
- files.push(path29.join(opts.outDir, "page-390.png"));
43694
+ fs32.writeFileSync(path30.join(opts.outDir, "page-390.png"), png);
43695
+ files.push(path30.join(opts.outDir, "page-390.png"));
43531
43696
  screenshots.push("page-390.png");
43532
43697
  } catch {
43533
43698
  }
@@ -43600,21 +43765,21 @@ async function verifyDesign(opts) {
43600
43765
  screenshots
43601
43766
  };
43602
43767
  const checked = VerificationSchema.parse(report2);
43603
- const jsonPath = path29.join(opts.outDir, "verification.json");
43604
- fs30.writeFileSync(jsonPath, JSON.stringify(checked, null, 2) + "\n");
43768
+ const jsonPath = path30.join(opts.outDir, "verification.json");
43769
+ fs32.writeFileSync(jsonPath, JSON.stringify(checked, null, 2) + "\n");
43605
43770
  files.push(jsonPath);
43606
43771
  const reportHtml = renderVerificationHtml(checked, opts.branding);
43607
- const htmlPath = path29.join(opts.outDir, "verification.html");
43608
- fs30.writeFileSync(htmlPath, reportHtml);
43772
+ const htmlPath = path30.join(opts.outDir, "verification.html");
43773
+ fs32.writeFileSync(htmlPath, reportHtml);
43609
43774
  files.push(htmlPath);
43610
43775
  return { report: checked, files, reportHtml };
43611
43776
  }
43612
- var fs30, path29, import_node_url, WEB_WIDTHS, SETTLE_SCROLL_JS, FOCUS_JS;
43777
+ var fs32, path30, import_node_url, WEB_WIDTHS, SETTLE_SCROLL_JS, FOCUS_JS;
43613
43778
  var init_verify = __esm({
43614
43779
  "src/command-os/design/verify.ts"() {
43615
43780
  "use strict";
43616
- fs30 = __toESM(require("node:fs"));
43617
- path29 = __toESM(require("node:path"));
43781
+ fs32 = __toESM(require("node:fs"));
43782
+ path30 = __toESM(require("node:path"));
43618
43783
  import_node_url = require("node:url");
43619
43784
  init_lint();
43620
43785
  init_probe();
@@ -43819,7 +43984,8 @@ function extractHtml(text) {
43819
43984
  function claudeGenerate(model = DEFAULT_MODEL, bin = "claude") {
43820
43985
  return (instruction, { cwd }) => new Promise((resolveP) => {
43821
43986
  const t0 = Date.now();
43822
- const child = (0, import_node_child_process4.spawn)(bin, ["-p", "--output-format", "json", "--max-turns", "1", "--tools", "", "--strict-mcp-config", "--model", model], {
43987
+ const cmd = claudeCommand(bin, ["-p", "--output-format", "json", "--max-turns", "1", "--tools", "", "--strict-mcp-config", "--model", model]);
43988
+ const child = (0, import_node_child_process4.spawn)(cmd.command, cmd.args, {
43823
43989
  cwd,
43824
43990
  env: { ...process.env, CLAUDECODE: "", CLAUDE_CODE_ENTRYPOINT: "" },
43825
43991
  stdio: ["pipe", "pipe", "pipe"]
@@ -43978,6 +44144,7 @@ var init_run = __esm({
43978
44144
  "src/command-os/design/run.ts"() {
43979
44145
  "use strict";
43980
44146
  import_node_child_process4 = require("node:child_process");
44147
+ init_claude_locate();
43981
44148
  import_node_fs7 = require("node:fs");
43982
44149
  import_node_path7 = require("node:path");
43983
44150
  init_brief();
@@ -44031,7 +44198,7 @@ function registerDesignStudioTools(server2) {
44031
44198
  const { dna, logoSvg, logoPng } = deriveBrandDna(raw, { name, slug: slug3, rights: { declaration: a.rights, at: (/* @__PURE__ */ new Date()).toISOString() }, browser, firecrawl: fc });
44032
44199
  if (a.rights === "none" && dna.logo) notes.push("Rights declared as none: the logo is stored for reference only; do not place it on a client-facing page.");
44033
44200
  const written = writeBrand(dna, { logoSvg, logoPng, screenshotPng: browser?.screenshotPng });
44034
- return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) => path30.basename(f)), notes };
44201
+ return { ok: true, brand: summarise2(dna), passes: { static: true, browser: Boolean(browser), firecrawl: Boolean(fc) }, spaShell: raw.spaShell, dir: written.dir, files: written.files.map((f) => path31.basename(f)), notes };
44035
44202
  }
44036
44203
  );
44037
44204
  safeTool(
@@ -44198,9 +44365,9 @@ function registerDesignStudioTools(server2) {
44198
44365
  "Check a written HTML file honours the output contract before grading: section and role markers present and consistent, no outside scripts, one h1 on a page, every image described. Deterministic; no browser. A file that fails this is not graded by verify_design.",
44199
44366
  { file: import_zod67.z.string().describe("Path to the .html file."), medium: import_zod67.z.enum(MEDIA) },
44200
44367
  async ({ file: file2, medium }) => {
44201
- const abs = path30.resolve(file2);
44202
- if (!fs31.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44203
- const r = lintDesignContract(fs31.readFileSync(abs, "utf8"), medium);
44368
+ const abs = path31.resolve(file2);
44369
+ if (!fs33.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44370
+ const r = lintDesignContract(fs33.readFileSync(abs, "utf8"), medium);
44204
44371
  return { ok: r.ok, problems: r.problems, warnings: r.warnings, facts: r.facts };
44205
44372
  }
44206
44373
  );
@@ -44221,7 +44388,7 @@ function registerDesignStudioTools(server2) {
44221
44388
  async (a) => {
44222
44389
  const brand = a.brandSlug ? houseLooks().find((d) => d.slug === a.brandSlug) ?? readBrand(slugify(a.brandSlug)) : void 0;
44223
44390
  const isUrl2 = /^https?:\/\//i.test(a.target);
44224
- const outDir = a.outDir ?? (isUrl2 ? path30.resolve(`verify-${new URL(a.target).hostname.replace(/[^a-z0-9.-]/gi, "_")}`) : path30.join(path30.dirname(path30.resolve(a.target)), `verify-${path30.basename(a.target, ".html")}`));
44391
+ const outDir = a.outDir ?? (isUrl2 ? path31.resolve(`verify-${new URL(a.target).hostname.replace(/[^a-z0-9.-]/gi, "_")}`) : path31.join(path31.dirname(path31.resolve(a.target)), `verify-${path31.basename(a.target, ".html")}`));
44225
44392
  const r = await verifyDesign({ target: a.target, medium: a.medium, brand, outDir, placement: a.placement, mode: a.mode, ratio: a.ratio, logo: a.logo, branding: readAgencyBranding() });
44226
44393
  const v = r.report;
44227
44394
  const failed = v.rules.filter((x) => x.state === "MEASURED-FAIL").map((x) => `${x.ruleId} ${x.name}: ${x.measured ?? ""}`);
@@ -44236,7 +44403,7 @@ function registerDesignStudioTools(server2) {
44236
44403
  judgments: v.rules.filter((x) => x.state === "ASSERTED").map((x) => `${x.ruleId} ${x.name}: ${x.detail ?? ""}`),
44237
44404
  notApplicable: v.rules.filter((x) => x.state === "N-A").map((x) => `${x.ruleId}: ${x.detail ?? ""}`),
44238
44405
  brandMatch: v.brandMatch,
44239
- report: path30.join(outDir, "verification.html"),
44406
+ report: path31.join(outDir, "verification.html"),
44240
44407
  files: r.files,
44241
44408
  ...a.placement === "ghl" && { builderOwned: builderOwnedRules().map((b) => `${b.rule.id} ${b.rule.name}: ${b.why}`) }
44242
44409
  };
@@ -44264,9 +44431,9 @@ function registerDesignStudioTools(server2) {
44264
44431
  if (!a.pageDataDownloadUrl && !a.file) throw new Error("Pass pageDataDownloadUrl (from get_page_full) or file.");
44265
44432
  let page;
44266
44433
  if (a.file) {
44267
- const abs = path30.resolve(a.file);
44268
- if (!fs31.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44269
- page = JSON.parse(fs31.readFileSync(abs, "utf8"));
44434
+ const abs = path31.resolve(a.file);
44435
+ if (!fs33.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44436
+ page = JSON.parse(fs33.readFileSync(abs, "utf8"));
44270
44437
  } else {
44271
44438
  const response = await fetch(a.pageDataDownloadUrl, { headers: { Accept: "application/json,*/*" } });
44272
44439
  if (!response.ok) throw new Error(`Could not download the page data: HTTP ${response.status}. Re-read get_page_full \u2014 the download URL expires.`);
@@ -44307,9 +44474,9 @@ function registerDesignStudioTools(server2) {
44307
44474
  if (!a.file && !a.text) throw new Error("Pass file or text.");
44308
44475
  let html2 = a.text ?? "";
44309
44476
  if (a.file) {
44310
- const abs = path30.resolve(a.file);
44311
- if (!fs31.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44312
- html2 = fs31.readFileSync(abs, "utf8");
44477
+ const abs = path31.resolve(a.file);
44478
+ if (!fs33.existsSync(abs)) throw new Error(`No file at ${abs}.`);
44479
+ html2 = fs33.readFileSync(abs, "utf8");
44313
44480
  }
44314
44481
  const r = await gradeCopy(html2, claudeGenerate(a.model), { icp: a.icp });
44315
44482
  if (!r.grade) {
@@ -44344,13 +44511,13 @@ function registerDesignStudioTools(server2) {
44344
44511
  }
44345
44512
  );
44346
44513
  }
44347
- var import_zod67, fs31, path30, summarise2;
44514
+ var import_zod67, fs33, path31, summarise2;
44348
44515
  var init_design_studio = __esm({
44349
44516
  "src/tools/design-studio.ts"() {
44350
44517
  "use strict";
44351
44518
  import_zod67 = require("zod");
44352
- fs31 = __toESM(require("node:fs"));
44353
- path30 = __toESM(require("node:path"));
44519
+ fs33 = __toESM(require("node:fs"));
44520
+ path31 = __toESM(require("node:path"));
44354
44521
  init_tool_helpers();
44355
44522
  init_extract();
44356
44523
  init_extract_browser();
@@ -44387,11 +44554,11 @@ var init_design_studio = __esm({
44387
44554
 
44388
44555
  // src/command-os/connectors/store.ts
44389
44556
  function connectorsPath() {
44390
- return path31.join(appDataDir(), "connectors.json");
44557
+ return path32.join(appDataDir(), "connectors.json");
44391
44558
  }
44392
44559
  function readFile() {
44393
44560
  try {
44394
- const raw = fs32.readFileSync(connectorsPath(), "utf-8");
44561
+ const raw = fs34.readFileSync(connectorsPath(), "utf-8");
44395
44562
  const parsed2 = JSON.parse(raw);
44396
44563
  if (!parsed2 || typeof parsed2 !== "object" || !parsed2.connectors) return { v: 1, connectors: {} };
44397
44564
  return parsed2;
@@ -44403,7 +44570,7 @@ function readForWrite() {
44403
44570
  const target = connectorsPath();
44404
44571
  let raw;
44405
44572
  try {
44406
- raw = fs32.readFileSync(target, "utf-8");
44573
+ raw = fs34.readFileSync(target, "utf-8");
44407
44574
  } catch (e) {
44408
44575
  if (e?.code === "ENOENT") return { v: 1, connectors: {} };
44409
44576
  throw new Error("Your connector file could not be read, so nothing was changed. Nothing was lost.");
@@ -44415,8 +44582,8 @@ function readForWrite() {
44415
44582
  } catch {
44416
44583
  const kept = `${target}.unreadable-${Date.now()}`;
44417
44584
  try {
44418
- fs32.copyFileSync(target, kept);
44419
- fs32.chmodSync(kept, 384);
44585
+ fs34.copyFileSync(target, kept);
44586
+ fs34.chmodSync(kept, 384);
44420
44587
  } catch {
44421
44588
  }
44422
44589
  throw new Error(
@@ -44430,23 +44597,23 @@ function writeFile(f) {
44430
44597
  const tmp = `${target}.${process.pid}.tmp`;
44431
44598
  let renamed = false;
44432
44599
  try {
44433
- fs32.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
44600
+ fs34.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
44434
44601
  try {
44435
- fs32.chmodSync(tmp, 384);
44602
+ fs34.chmodSync(tmp, 384);
44436
44603
  } catch {
44437
44604
  }
44438
- fs32.renameSync(tmp, target);
44605
+ fs34.renameSync(tmp, target);
44439
44606
  renamed = true;
44440
44607
  } finally {
44441
44608
  if (!renamed) {
44442
44609
  try {
44443
- fs32.unlinkSync(tmp);
44610
+ fs34.unlinkSync(tmp);
44444
44611
  } catch {
44445
44612
  }
44446
44613
  }
44447
44614
  }
44448
44615
  try {
44449
- fs32.chmodSync(target, 384);
44616
+ fs34.chmodSync(target, 384);
44450
44617
  } catch {
44451
44618
  }
44452
44619
  }
@@ -44480,12 +44647,12 @@ async function withKey(system, fn) {
44480
44647
  if (!s) throw new Error(`${system} is not connected on this machine.`);
44481
44648
  return fn(s.key);
44482
44649
  }
44483
- var fs32, path31, hintOf;
44650
+ var fs34, path32, hintOf;
44484
44651
  var init_store3 = __esm({
44485
44652
  "src/command-os/connectors/store.ts"() {
44486
44653
  "use strict";
44487
- fs32 = __toESM(require("node:fs"));
44488
- path31 = __toESM(require("node:path"));
44654
+ fs34 = __toESM(require("node:fs"));
44655
+ path32 = __toESM(require("node:path"));
44489
44656
  init_credentials_store();
44490
44657
  hintOf = (key) => `\u2026${String(key).slice(-4)}`;
44491
44658
  }
@@ -45141,8 +45308,8 @@ var init_to_engagements = __esm({
45141
45308
 
45142
45309
  // src/tools/connectors.ts
45143
45310
  function stripeFetcher(key) {
45144
- return async (path39, query) => {
45145
- const url = new URL(`https://api.stripe.com${path39}`);
45311
+ return async (path40, query) => {
45312
+ const url = new URL(`https://api.stripe.com${path40}`);
45146
45313
  for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
45147
45314
  const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
45148
45315
  if (!res.ok) {
@@ -45151,13 +45318,13 @@ function stripeFetcher(key) {
45151
45318
  return res.json();
45152
45319
  };
45153
45320
  }
45154
- async function listAll(f, path39, extra = {}) {
45321
+ async function listAll(f, path40, extra = {}) {
45155
45322
  const out = [];
45156
45323
  let after;
45157
45324
  for (let page = 0; page < 20; page++) {
45158
45325
  const q2 = { limit: "100", ...extra };
45159
45326
  if (after) q2.starting_after = after;
45160
- const res = await f(path39, q2);
45327
+ const res = await f(path40, q2);
45161
45328
  const data = res?.data ?? [];
45162
45329
  out.push(...data);
45163
45330
  if (!res?.has_more || !data.length) break;
@@ -45430,7 +45597,7 @@ var init_connectors = __esm({
45430
45597
  // src/command-os/connectors/google-calendar-store.ts
45431
45598
  function read() {
45432
45599
  try {
45433
- return SecretSchema.parse(JSON.parse(fs33.readFileSync(googleCalendarPath(), "utf8")));
45600
+ return SecretSchema.parse(JSON.parse(fs35.readFileSync(googleCalendarPath(), "utf8")));
45434
45601
  } catch (error) {
45435
45602
  if (error.code === "ENOENT") return null;
45436
45603
  throw new Error("The Google Calendar sign-in on this machine could not be read. Connect your Google Calendar again.");
@@ -45453,16 +45620,16 @@ function saveCalendarSecret(secret) {
45453
45620
  try {
45454
45621
  const validated = SecretSchema.parse(secret);
45455
45622
  const dir = ensureAppDataDir();
45456
- if (process.platform !== "win32") fs33.chmodSync(dir, 448);
45623
+ if (process.platform !== "win32") fs35.chmodSync(dir, 448);
45457
45624
  tmp = `${googleCalendarPath()}.${(0, import_node_crypto9.randomUUID)()}.tmp`;
45458
- fs33.writeFileSync(tmp, JSON.stringify(validated) + "\n", { mode: 384, flag: "wx" });
45459
- fs33.renameSync(tmp, googleCalendarPath());
45625
+ fs35.writeFileSync(tmp, JSON.stringify(validated) + "\n", { mode: 384, flag: "wx" });
45626
+ fs35.renameSync(tmp, googleCalendarPath());
45460
45627
  } catch {
45461
45628
  throw new Error("The Google Calendar sign-in could not be saved privately on this machine. Nothing new was saved.");
45462
45629
  } finally {
45463
45630
  if (tmp) {
45464
45631
  try {
45465
- fs33.unlinkSync(tmp);
45632
+ fs35.unlinkSync(tmp);
45466
45633
  } catch {
45467
45634
  }
45468
45635
  }
@@ -45470,19 +45637,19 @@ function saveCalendarSecret(secret) {
45470
45637
  }
45471
45638
  function removeCalendarSecret() {
45472
45639
  try {
45473
- fs33.unlinkSync(googleCalendarPath());
45640
+ fs35.unlinkSync(googleCalendarPath());
45474
45641
  return true;
45475
45642
  } catch (error) {
45476
45643
  if (error.code === "ENOENT") return false;
45477
45644
  throw new Error("The Google Calendar sign-in could not be removed from this machine.");
45478
45645
  }
45479
45646
  }
45480
- var fs33, path32, import_node_crypto9, import_zod69, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
45647
+ var fs35, path33, import_node_crypto9, import_zod69, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
45481
45648
  var init_google_calendar_store = __esm({
45482
45649
  "src/command-os/connectors/google-calendar-store.ts"() {
45483
45650
  "use strict";
45484
- fs33 = __toESM(require("node:fs"));
45485
- path32 = __toESM(require("node:path"));
45651
+ fs35 = __toESM(require("node:fs"));
45652
+ path33 = __toESM(require("node:path"));
45486
45653
  import_node_crypto9 = require("node:crypto");
45487
45654
  import_zod69 = require("zod");
45488
45655
  init_credentials_store();
@@ -45494,7 +45661,7 @@ var init_google_calendar_store = __esm({
45494
45661
  scope: import_zod69.z.literal(CALENDAR_SCOPE),
45495
45662
  connectedAt: import_zod69.z.string()
45496
45663
  });
45497
- googleCalendarPath = () => path32.join(appDataDir(), "google-calendar.json");
45664
+ googleCalendarPath = () => path33.join(appDataDir(), "google-calendar.json");
45498
45665
  }
45499
45666
  });
45500
45667
 
@@ -45596,16 +45763,16 @@ function createGoogleCalendar(deps = {}) {
45596
45763
  requireOnlyCalendar(result.scope);
45597
45764
  return result.access_token;
45598
45765
  }
45599
- async function pages(path39, access, item, query = {}) {
45766
+ async function pages(path40, access, item, query = {}) {
45600
45767
  const result = [];
45601
45768
  const seen = /* @__PURE__ */ new Set();
45602
45769
  let pageToken;
45603
45770
  for (let page = 0; page < 100; page++) {
45604
- const url = new URL(`${API}${path39}`);
45771
+ const url = new URL(`${API}${path40}`);
45605
45772
  for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
45606
45773
  if (pageToken) url.searchParams.set("pageToken", pageToken);
45607
45774
  const data = parsed(
45608
- import_zod70.z.object({ items: import_zod70.z.array(item).optional(), nextPageToken: import_zod70.z.string().optional(), kind: import_zod70.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path39.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
45775
+ import_zod70.z.object({ items: import_zod70.z.array(item).optional(), nextPageToken: import_zod70.z.string().optional(), kind: import_zod70.z.string().optional() }).refine((p) => p.items !== void 0 || p.kind === (path40.endsWith("/events") ? "calendar#events" : "calendar#calendarList")),
45609
45776
  await request(url.href, { headers: { Authorization: `Bearer ${access}` } })
45610
45777
  );
45611
45778
  result.push(...data.items ?? []);
@@ -45907,10 +46074,10 @@ function score(answers, available) {
45907
46074
  }
45908
46075
  var hours = minutes * times / 60;
45909
46076
  if (hours <= 0) return;
45910
- var runnable = t.tools.filter(function(n) {
46077
+ var runnable2 = t.tools.filter(function(n) {
45911
46078
  return known ? have[n] === true : true;
45912
46079
  });
45913
- var usable = t.tools.length > 0 && runnable.length === t.tools.length;
46080
+ var usable = t.tools.length > 0 && runnable2.length === t.tools.length;
45914
46081
  scored.push({
45915
46082
  task: t.id,
45916
46083
  label: t.label,
@@ -46012,11 +46179,11 @@ var init_audit = __esm({
46012
46179
  // src/command-os/delivery-audit/store.ts
46013
46180
  function deliveryAuditPath() {
46014
46181
  const override = process.env[DELIVERY_AUDIT_ENV];
46015
- return override && override.trim() ? override : path33.join(appDataDir(), "delivery-audit.json");
46182
+ return override && override.trim() ? override : path34.join(appDataDir(), "delivery-audit.json");
46016
46183
  }
46017
46184
  function readDeliveryAudit() {
46018
46185
  try {
46019
- const parsed2 = JSON.parse(fs34.readFileSync(deliveryAuditPath(), "utf-8"));
46186
+ const parsed2 = JSON.parse(fs36.readFileSync(deliveryAuditPath(), "utf-8"));
46020
46187
  if (!parsed2 || typeof parsed2 !== "object" || !parsed2.answers || typeof parsed2.answers !== "object") return { ...EMPTY3 };
46021
46188
  return { v: 1, answers: parsed2.answers, updated_at: parsed2.updated_at };
46022
46189
  } catch {
@@ -46027,7 +46194,7 @@ function readForWrite2() {
46027
46194
  const target = deliveryAuditPath();
46028
46195
  let raw;
46029
46196
  try {
46030
- raw = fs34.readFileSync(target, "utf-8");
46197
+ raw = fs36.readFileSync(target, "utf-8");
46031
46198
  } catch (e) {
46032
46199
  if (e?.code === "ENOENT") return { ...EMPTY3 };
46033
46200
  throw new Error("Your saved audit could not be read, so nothing was changed. Nothing was lost.");
@@ -46039,7 +46206,7 @@ function readForWrite2() {
46039
46206
  } catch {
46040
46207
  const kept = `${target}.unreadable-${Date.now()}`;
46041
46208
  try {
46042
- fs34.copyFileSync(target, kept);
46209
+ fs36.copyFileSync(target, kept);
46043
46210
  } catch {
46044
46211
  }
46045
46212
  throw new Error(`Your saved audit is damaged, so nothing was written over it. A copy is at ${kept}. Start again with reset:true when you are ready.`);
@@ -46051,13 +46218,13 @@ function writeFile2(f) {
46051
46218
  const tmp = `${target}.${process.pid}.tmp`;
46052
46219
  let renamed = false;
46053
46220
  try {
46054
- fs34.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
46055
- fs34.renameSync(tmp, target);
46221
+ fs36.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
46222
+ fs36.renameSync(tmp, target);
46056
46223
  renamed = true;
46057
46224
  } finally {
46058
46225
  if (!renamed) {
46059
46226
  try {
46060
- fs34.unlinkSync(tmp);
46227
+ fs36.unlinkSync(tmp);
46061
46228
  } catch {
46062
46229
  }
46063
46230
  }
@@ -46078,12 +46245,12 @@ function resetDeliveryAudit(now = () => /* @__PURE__ */ new Date()) {
46078
46245
  writeFile2(next);
46079
46246
  return next;
46080
46247
  }
46081
- var fs34, path33, EMPTY3, DELIVERY_AUDIT_ENV;
46248
+ var fs36, path34, EMPTY3, DELIVERY_AUDIT_ENV;
46082
46249
  var init_store4 = __esm({
46083
46250
  "src/command-os/delivery-audit/store.ts"() {
46084
46251
  "use strict";
46085
- fs34 = __toESM(require("node:fs"));
46086
- path33 = __toESM(require("node:path"));
46252
+ fs36 = __toESM(require("node:fs"));
46253
+ path34 = __toESM(require("node:path"));
46087
46254
  init_credentials_store();
46088
46255
  EMPTY3 = { v: 1, answers: {} };
46089
46256
  DELIVERY_AUDIT_ENV = "GHL_MCP_DELIVERY_AUDIT";
@@ -46398,7 +46565,7 @@ var init_automation = __esm({
46398
46565
  // src/command-os/build/store.ts
46399
46566
  function automationsPath() {
46400
46567
  const override = process.env[AUTOMATIONS_ENV];
46401
- return override && override.trim() ? override : path34.join(appDataDir(), "automations.json");
46568
+ return override && override.trim() ? override : path35.join(appDataDir(), "automations.json");
46402
46569
  }
46403
46570
  function parse4(raw) {
46404
46571
  const p = JSON.parse(raw);
@@ -46407,7 +46574,7 @@ function parse4(raw) {
46407
46574
  }
46408
46575
  function readAutomations() {
46409
46576
  try {
46410
- return parse4(fs35.readFileSync(automationsPath(), "utf-8")).builds;
46577
+ return parse4(fs37.readFileSync(automationsPath(), "utf-8")).builds;
46411
46578
  } catch {
46412
46579
  return { ...EMPTY4.builds };
46413
46580
  }
@@ -46416,7 +46583,7 @@ function readForWrite3() {
46416
46583
  const target = automationsPath();
46417
46584
  let raw;
46418
46585
  try {
46419
- raw = fs35.readFileSync(target, "utf-8");
46586
+ raw = fs37.readFileSync(target, "utf-8");
46420
46587
  } catch (e) {
46421
46588
  if (e?.code === "ENOENT") return { v: 1, builds: {} };
46422
46589
  throw new Error("Your builds file could not be read, so nothing was changed. Nothing was lost.");
@@ -46426,7 +46593,7 @@ function readForWrite3() {
46426
46593
  } catch {
46427
46594
  const kept = `${target}.unreadable-${Date.now()}`;
46428
46595
  try {
46429
- fs35.copyFileSync(target, kept);
46596
+ fs37.copyFileSync(target, kept);
46430
46597
  } catch {
46431
46598
  }
46432
46599
  throw new Error(
@@ -46440,13 +46607,13 @@ function writeFile3(f) {
46440
46607
  const tmp = `${target}.${process.pid}.tmp`;
46441
46608
  let renamed = false;
46442
46609
  try {
46443
- fs35.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
46444
- fs35.renameSync(tmp, target);
46610
+ fs37.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
46611
+ fs37.renameSync(tmp, target);
46445
46612
  renamed = true;
46446
46613
  } finally {
46447
46614
  if (!renamed) {
46448
46615
  try {
46449
- fs35.unlinkSync(tmp);
46616
+ fs37.unlinkSync(tmp);
46450
46617
  } catch {
46451
46618
  }
46452
46619
  }
@@ -46472,12 +46639,12 @@ function newAutomationId(now = () => /* @__PURE__ */ new Date()) {
46472
46639
  }
46473
46640
  return id;
46474
46641
  }
46475
- var fs35, path34, AUTOMATIONS_ENV, EMPTY4;
46642
+ var fs37, path35, AUTOMATIONS_ENV, EMPTY4;
46476
46643
  var init_store5 = __esm({
46477
46644
  "src/command-os/build/store.ts"() {
46478
46645
  "use strict";
46479
- fs35 = __toESM(require("node:fs"));
46480
- path34 = __toESM(require("node:path"));
46646
+ fs37 = __toESM(require("node:fs"));
46647
+ path35 = __toESM(require("node:path"));
46481
46648
  init_credentials_store();
46482
46649
  AUTOMATIONS_ENV = "GHL_MCP_AUTOMATIONS";
46483
46650
  EMPTY4 = { v: 1, builds: {} };
@@ -46773,7 +46940,7 @@ var init_client_week = __esm({
46773
46940
  // src/board-roster.ts
46774
46941
  function readBoardState() {
46775
46942
  try {
46776
- const raw = JSON.parse(fs36.readFileSync(path35.join(appDataDir(), "cockpit-state.json"), "utf8"));
46943
+ const raw = JSON.parse(fs38.readFileSync(path36.join(appDataDir(), "cockpit-state.json"), "utf8"));
46777
46944
  if (raw && typeof raw === "object" && raw.clients && typeof raw.clients === "object") return raw;
46778
46945
  } catch {
46779
46946
  }
@@ -46791,12 +46958,12 @@ function knownClients(registry2, state) {
46791
46958
  }
46792
46959
  return [...out.values()];
46793
46960
  }
46794
- var fs36, path35;
46961
+ var fs38, path36;
46795
46962
  var init_board_roster = __esm({
46796
46963
  "src/board-roster.ts"() {
46797
46964
  "use strict";
46798
- fs36 = __toESM(require("fs"));
46799
- path35 = __toESM(require("path"));
46965
+ fs38 = __toESM(require("fs"));
46966
+ path36 = __toESM(require("path"));
46800
46967
  init_credentials_store();
46801
46968
  init_client_engagements();
46802
46969
  }
@@ -48831,9 +48998,9 @@ function presetForBusinessType(type) {
48831
48998
  return "generic";
48832
48999
  }
48833
49000
  }
48834
- function setPath(target, path39, value) {
49001
+ function setPath(target, path40, value) {
48835
49002
  if (value === void 0) return;
48836
- const parts = path39.split(".");
49003
+ const parts = path40.split(".");
48837
49004
  let node = target;
48838
49005
  for (let i = 0; i < parts.length - 1; i++) {
48839
49006
  const k = parts[i];
@@ -49904,17 +50071,17 @@ function planFromPreset(input) {
49904
50071
  }
49905
50072
  function presetsRoot() {
49906
50073
  const candidates = [
49907
- path36.join(__dirname, "..", "skills", "blueprint", "presets"),
50074
+ path37.join(__dirname, "..", "skills", "blueprint", "presets"),
49908
50075
  // dist/ → package root
49909
- path36.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
50076
+ path37.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
49910
50077
  // src/intake-to-build in dev
49911
- path36.join(process.cwd(), "skills", "blueprint", "presets")
50078
+ path37.join(process.cwd(), "skills", "blueprint", "presets")
49912
50079
  ];
49913
- return candidates.find((c) => fs37.existsSync(c)) ?? null;
50080
+ return candidates.find((c) => fs39.existsSync(c)) ?? null;
49914
50081
  }
49915
50082
  function readPresetFile(dir, base) {
49916
50083
  try {
49917
- const raw = JSON.parse(fs37.readFileSync(path36.join(dir, `${base}.preset.json`), "utf8"));
50084
+ const raw = JSON.parse(fs39.readFileSync(path37.join(dir, `${base}.preset.json`), "utf8"));
49918
50085
  if (raw && typeof raw === "object" && typeof raw.presetId === "string" && raw.skeleton) return raw;
49919
50086
  return null;
49920
50087
  } catch {
@@ -49926,7 +50093,7 @@ function loadPresetChoice(requested, briefPreset) {
49926
50093
  if (!dir) {
49927
50094
  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." };
49928
50095
  }
49929
- const bases = fs37.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
50096
+ const bases = fs39.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
49930
50097
  const tryMatch = (want) => {
49931
50098
  const norm6 = slugify2(want);
49932
50099
  for (const base of bases) {
@@ -49957,12 +50124,12 @@ function loadPresetChoice(requested, briefPreset) {
49957
50124
  if (generic) return { ok: true, preset: generic, file: "generic-client.preset.json", note: "Using the generic preset." };
49958
50125
  return { ok: false, error: "No usable preset file was found in the library." };
49959
50126
  }
49960
- var fs37, path36, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
50127
+ var fs39, path37, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
49961
50128
  var init_plan_from_preset = __esm({
49962
50129
  "src/intake-to-build/plan-from-preset.ts"() {
49963
50130
  "use strict";
49964
- fs37 = __toESM(require("fs"));
49965
- path36 = __toESM(require("path"));
50131
+ fs39 = __toESM(require("fs"));
50132
+ path37 = __toESM(require("path"));
49966
50133
  init_plan();
49967
50134
  init_brief2();
49968
50135
  init_normalizer();
@@ -51186,13 +51353,13 @@ ${text2}`);
51186
51353
  };
51187
51354
  }
51188
51355
  function makePipelineApi(builderClient) {
51189
- return async (method, path39, body) => {
51190
- const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path39}`;
51356
+ return async (method, path40, body) => {
51357
+ const url = `https://backend.leadconnectorhq.com/opportunities/pipelines${path40}`;
51191
51358
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
51192
51359
  const response = await builderRequest(builderClient, method, url, sendsBody ? { body } : {});
51193
51360
  if (!response.ok) {
51194
51361
  const text2 = await response.text();
51195
- throw new Error(`Pipeline API ${response.status}: ${method} ${path39}
51362
+ throw new Error(`Pipeline API ${response.status}: ${method} ${path40}
51196
51363
  ${text2}`);
51197
51364
  }
51198
51365
  const text = await response.text();
@@ -51205,8 +51372,8 @@ ${text2}`);
51205
51372
  };
51206
51373
  }
51207
51374
  function makeFunnelApi(builderClient) {
51208
- return async (method, path39, body) => {
51209
- const url = `https://backend.leadconnectorhq.com/funnels${path39}`;
51375
+ return async (method, path40, body) => {
51376
+ const url = `https://backend.leadconnectorhq.com/funnels${path40}`;
51210
51377
  const sendsBody = body !== void 0 && (method === "POST" || method === "PUT");
51211
51378
  const response = await builderRequest(builderClient, method, url, {
51212
51379
  ...sendsBody ? { body } : {},
@@ -51219,7 +51386,7 @@ function makeFunnelApi(builderClient) {
51219
51386
  });
51220
51387
  if (!response.ok) {
51221
51388
  const text2 = await response.text();
51222
- throw new Error(`Funnel API ${response.status}: ${method} ${path39}
51389
+ throw new Error(`Funnel API ${response.status}: ${method} ${path40}
51223
51390
  ${text2.slice(0, 300)}`);
51224
51391
  }
51225
51392
  const text = await response.text();
@@ -52814,8 +52981,8 @@ __export(index_exports, {
52814
52981
  });
52815
52982
  module.exports = __toCommonJS(index_exports);
52816
52983
  var dotenv2 = __toESM(require("dotenv"));
52817
- var path38 = __toESM(require("path"));
52818
- var fs39 = __toESM(require("fs"));
52984
+ var path39 = __toESM(require("path"));
52985
+ var fs41 = __toESM(require("fs"));
52819
52986
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
52820
52987
 
52821
52988
  // src/version-compare.ts
@@ -52887,9 +53054,9 @@ var import_zod82 = require("zod");
52887
53054
 
52888
53055
  // src/skill-installer.ts
52889
53056
  var import_node_crypto12 = require("node:crypto");
52890
- var fs38 = __toESM(require("node:fs"));
52891
- var path37 = __toESM(require("node:path"));
52892
- var os6 = __toESM(require("node:os"));
53057
+ var fs40 = __toESM(require("node:fs"));
53058
+ var path38 = __toESM(require("node:path"));
53059
+ var os7 = __toESM(require("node:os"));
52893
53060
  function sha2562(buf) {
52894
53061
  return (0, import_node_crypto12.createHash)("sha256").update(buf).digest("hex");
52895
53062
  }
@@ -52903,9 +53070,9 @@ function isOlderRelease(candidate, installed) {
52903
53070
  return false;
52904
53071
  }
52905
53072
  function bundledSkillsDir(baseDir) {
52906
- const candidate = path37.resolve(baseDir, "..", "skills");
53073
+ const candidate = path38.resolve(baseDir, "..", "skills");
52907
53074
  try {
52908
- return fs38.statSync(candidate).isDirectory() ? candidate : null;
53075
+ return fs40.statSync(candidate).isDirectory() ? candidate : null;
52909
53076
  } catch {
52910
53077
  return null;
52911
53078
  }
@@ -52913,10 +53080,10 @@ function bundledSkillsDir(baseDir) {
52913
53080
  function listFiles(root) {
52914
53081
  const out = [];
52915
53082
  const walk = (dir) => {
52916
- for (const entry of fs38.readdirSync(dir, { withFileTypes: true })) {
52917
- const full = path37.join(dir, entry.name);
53083
+ for (const entry of fs40.readdirSync(dir, { withFileTypes: true })) {
53084
+ const full = path38.join(dir, entry.name);
52918
53085
  if (entry.isDirectory()) walk(full);
52919
- else if (entry.isFile()) out.push(path37.relative(root, full));
53086
+ else if (entry.isFile()) out.push(path38.relative(root, full));
52920
53087
  }
52921
53088
  };
52922
53089
  walk(root);
@@ -52924,7 +53091,7 @@ function listFiles(root) {
52924
53091
  }
52925
53092
  function readMarker(markerPath) {
52926
53093
  try {
52927
- const parsed2 = JSON.parse(fs38.readFileSync(markerPath, "utf8"));
53094
+ const parsed2 = JSON.parse(fs40.readFileSync(markerPath, "utf8"));
52928
53095
  return parsed2 && typeof parsed2 === "object" && parsed2.files ? parsed2 : null;
52929
53096
  } catch {
52930
53097
  return null;
@@ -52938,35 +53105,35 @@ function installBundledSkills(opts) {
52938
53105
  skippedOlderPackage: [],
52939
53106
  unchanged: [],
52940
53107
  errors: [],
52941
- targetDir: opts.targetDir ?? path37.join(os6.homedir(), ".claude", "skills"),
53108
+ targetDir: opts.targetDir ?? path38.join(os7.homedir(), ".claude", "skills"),
52942
53109
  bundledDir: null
52943
53110
  };
52944
53111
  const bundled = bundledSkillsDir(opts.baseDir);
52945
53112
  result.bundledDir = bundled;
52946
53113
  if (!bundled) return result;
52947
53114
  try {
52948
- fs38.mkdirSync(result.targetDir, { recursive: true });
53115
+ fs40.mkdirSync(result.targetDir, { recursive: true });
52949
53116
  } catch (e) {
52950
53117
  result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
52951
53118
  return result;
52952
53119
  }
52953
- const markerPath = path37.join(result.targetDir, ".ghl-command-skills.json");
53120
+ const markerPath = path38.join(result.targetDir, ".ghl-command-skills.json");
52954
53121
  const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
52955
53122
  const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
52956
53123
  for (const rel of listFiles(bundled)) {
52957
53124
  try {
52958
- const src = fs38.readFileSync(path37.join(bundled, rel));
52959
- const dest = path37.join(result.targetDir, rel);
53125
+ const src = fs40.readFileSync(path38.join(bundled, rel));
53126
+ const dest = path38.join(result.targetDir, rel);
52960
53127
  const srcHash = sha2562(src);
52961
53128
  let destBuf = null;
52962
53129
  try {
52963
- destBuf = fs38.readFileSync(dest);
53130
+ destBuf = fs40.readFileSync(dest);
52964
53131
  } catch {
52965
53132
  destBuf = null;
52966
53133
  }
52967
53134
  if (destBuf === null) {
52968
- fs38.mkdirSync(path37.dirname(dest), { recursive: true });
52969
- fs38.writeFileSync(dest, src);
53135
+ fs40.mkdirSync(path38.dirname(dest), { recursive: true });
53136
+ fs40.writeFileSync(dest, src);
52970
53137
  newMarker.files[rel] = srcHash;
52971
53138
  result.installed.push(rel);
52972
53139
  continue;
@@ -52983,7 +53150,7 @@ function installBundledSkills(opts) {
52983
53150
  newMarker.packageVersion = marker.packageVersion;
52984
53151
  result.skippedOlderPackage.push(rel);
52985
53152
  } else if (lastWritten && destHash === lastWritten) {
52986
- fs38.writeFileSync(dest, src);
53153
+ fs40.writeFileSync(dest, src);
52987
53154
  newMarker.files[rel] = srcHash;
52988
53155
  result.updated.push(rel);
52989
53156
  } else {
@@ -52994,20 +53161,27 @@ function installBundledSkills(opts) {
52994
53161
  }
52995
53162
  }
52996
53163
  try {
52997
- fs38.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
53164
+ fs40.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
52998
53165
  } catch (e) {
52999
53166
  result.errors.push(`marker write failed: ${String(e)}`);
53000
53167
  }
53001
53168
  return result;
53002
53169
  }
53170
+ function skillNames(files) {
53171
+ return [...new Set(files.map((rel) => rel.split(/[\\/]/)[0]))].sort();
53172
+ }
53003
53173
  function summarizeInstall(r) {
53004
53174
  if (!r.bundledDir) return "";
53005
53175
  const bits = [];
53006
- if (r.installed.length) bits.push(`${r.installed.length} installed`);
53007
- if (r.updated.length) bits.push(`${r.updated.length} updated`);
53008
- if (r.skippedUserModified.length) bits.push(`${r.skippedUserModified.length} kept (your edits)`);
53009
- if (r.skippedOlderPackage.length) bits.push(`${r.skippedOlderPackage.length} kept (newer than this version)`);
53010
- if (r.errors.length) bits.push(`${r.errors.length} errors`);
53176
+ const bucket = (files, label) => {
53177
+ const names = skillNames(files);
53178
+ if (names.length) bits.push(`${names.length} ${label} (${names.join(", ")})`);
53179
+ };
53180
+ bucket(r.installed, "installed");
53181
+ bucket(r.updated, "updated");
53182
+ bucket(r.skippedUserModified, "kept with your edits");
53183
+ bucket(r.skippedOlderPackage, "kept, newer than this version");
53184
+ if (r.errors.length) bits.push(`${r.errors.length} file error${r.errors.length === 1 ? "" : "s"}`);
53011
53185
  if (bits.length === 0) return "";
53012
53186
  return `Skills: ${bits.join(", ")} \u2192 ${r.targetDir}`;
53013
53187
  }
@@ -53033,6 +53207,8 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
53033
53207
  return jsonResponse({
53034
53208
  ok: result.errors.length === 0,
53035
53209
  targetDir: result.targetDir,
53210
+ // Skills, by name, before the per-file detail (BUG-2026-09-14-03).
53211
+ skills: { installed: skillNames(result.installed), updated: skillNames(result.updated), keptYourEdits: skillNames(result.skippedUserModified) },
53036
53212
  installed: result.installed,
53037
53213
  updated: result.updated,
53038
53214
  keptYourEdits: result.skippedUserModified,
@@ -53080,11 +53256,11 @@ function agencyPrimer(p) {
53080
53256
  if (staff.length) lines.push(`Their team: ${staff.join("; ")}.`);
53081
53257
  const sops = (p.delivery?.sops ?? []).filter(Boolean);
53082
53258
  if (sops.length) lines.push(`How they deliver, in their words: ${sops.map((s) => `"${s}"`).join(" ")}`);
53083
- const os7 = p.agencyOs;
53084
- if (os7?.method) lines.push(`Their method: ${os7.method}`);
53085
- if (os7?.voice) lines.push(`How they sound, and how you should sound for them: ${os7.voice}`);
53086
- if (os7?.ica?.who) lines.push(`Who they serve: ${os7.ica.who}`);
53087
- if (os7?.offer?.name || os7?.offer?.promise) lines.push(`Their core offer: ${[os7.offer?.name, os7.offer?.promise].filter(Boolean).join(" \u2014 ")}`);
53259
+ const os8 = p.agencyOs;
53260
+ if (os8?.method) lines.push(`Their method: ${os8.method}`);
53261
+ if (os8?.voice) lines.push(`How they sound, and how you should sound for them: ${os8.voice}`);
53262
+ if (os8?.ica?.who) lines.push(`Who they serve: ${os8.ica.who}`);
53263
+ if (os8?.offer?.name || os8?.offer?.promise) lines.push(`Their core offer: ${[os8.offer?.name, os8.offer?.promise].filter(Boolean).join(" \u2014 ")}`);
53088
53264
  if (!lines.length) return void 0;
53089
53265
  lines.push("These are the agency's own details, not any client's. Client-by-client records are a tool call away, not in here.");
53090
53266
  const text = lines.join("\n");
@@ -53141,7 +53317,7 @@ dotenv2.config();
53141
53317
  setPkgVersion(pkg.version);
53142
53318
  {
53143
53319
  const configDirOverride = process.env.GHL_MCP_CONFIG_DIR?.trim();
53144
- if (configDirOverride && !path38.isAbsolute(configDirOverride)) {
53320
+ if (configDirOverride && !path39.isAbsolute(configDirOverride)) {
53145
53321
  process.stderr.write(
53146
53322
  `[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.
53147
53323
  `
@@ -53154,20 +53330,20 @@ process.on("unhandledRejection", (reason2) => {
53154
53330
  `);
53155
53331
  });
53156
53332
  function hardenSecretFilePerms() {
53157
- const repoDir = path38.resolve(__dirname, "..");
53333
+ const repoDir = path39.resolve(__dirname, "..");
53158
53334
  const candidates = [
53159
- { file: path38.join(repoDir, "start-mcp.sh"), mode: 448 },
53335
+ { file: path39.join(repoDir, "start-mcp.sh"), mode: 448 },
53160
53336
  // Legacy registry location (pre-migration); new location lives in app-data.
53161
- { file: path38.join(repoDir, ".ghl-tokens.json"), mode: 384 },
53337
+ { file: path39.join(repoDir, ".ghl-tokens.json"), mode: 384 },
53162
53338
  { file: tokenRegistryPath(), mode: 384 }
53163
53339
  ];
53164
53340
  for (const { file: file2, mode } of candidates) {
53165
53341
  let current;
53166
53342
  try {
53167
- if (!fs39.existsSync(file2)) continue;
53168
- current = fs39.statSync(file2).mode & 511;
53343
+ if (!fs41.existsSync(file2)) continue;
53344
+ current = fs41.statSync(file2).mode & 511;
53169
53345
  if (current !== mode) {
53170
- fs39.chmodSync(file2, mode);
53346
+ fs41.chmodSync(file2, mode);
53171
53347
  }
53172
53348
  } catch (error) {
53173
53349
  const message = error instanceof Error ? error.message : String(error);