@elitedcs/ghl-mcp 3.83.6 → 3.83.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.md +1 -1
- package/dist/index.js +901 -713
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -545,7 +545,14 @@ var init_account_rules = __esm({
|
|
|
545
545
|
});
|
|
546
546
|
|
|
547
547
|
// src/ghl-client.ts
|
|
548
|
-
|
|
548
|
+
function requestError(error) {
|
|
549
|
+
if (error !== null && typeof error === "object") requestErrors.add(error);
|
|
550
|
+
return error;
|
|
551
|
+
}
|
|
552
|
+
function isGHLRequestError(error) {
|
|
553
|
+
return error !== null && typeof error === "object" && requestErrors.has(error);
|
|
554
|
+
}
|
|
555
|
+
var GHL_BASE_URL, GHL_API_VERSION, MAX_RETRIES, BASE_DELAY_MS, requestErrors, GHLClient;
|
|
549
556
|
var init_ghl_client = __esm({
|
|
550
557
|
"src/ghl-client.ts"() {
|
|
551
558
|
"use strict";
|
|
@@ -556,6 +563,7 @@ var init_ghl_client = __esm({
|
|
|
556
563
|
GHL_API_VERSION = "2021-07-28";
|
|
557
564
|
MAX_RETRIES = 3;
|
|
558
565
|
BASE_DELAY_MS = 500;
|
|
566
|
+
requestErrors = /* @__PURE__ */ new WeakSet();
|
|
559
567
|
GHLClient = class _GHLClient {
|
|
560
568
|
apiKey;
|
|
561
569
|
defaultLocationId;
|
|
@@ -639,7 +647,7 @@ var init_ghl_client = __esm({
|
|
|
639
647
|
} catch (error) {
|
|
640
648
|
clearTimeout(timeout);
|
|
641
649
|
if (error instanceof Error && error.name === "AbortError") {
|
|
642
|
-
throw new Error(`Request timeout (30s): ${method} ${path39}`);
|
|
650
|
+
throw requestError(new Error(`Request timeout (30s): ${method} ${path39}`));
|
|
643
651
|
}
|
|
644
652
|
if (!inBuildRequestScope() && !options.noRetry && attempt < MAX_RETRIES) {
|
|
645
653
|
const delay5 = computeRetryDelay(null, attempt, BASE_DELAY_MS);
|
|
@@ -648,7 +656,7 @@ var init_ghl_client = __esm({
|
|
|
648
656
|
await new Promise((r) => setTimeout(r, delay5));
|
|
649
657
|
return this.request(method, path39, options, attempt + 1);
|
|
650
658
|
}
|
|
651
|
-
throw error;
|
|
659
|
+
throw requestError(error);
|
|
652
660
|
} finally {
|
|
653
661
|
clearTimeout(timeout);
|
|
654
662
|
}
|
|
@@ -665,12 +673,17 @@ var init_ghl_client = __esm({
|
|
|
665
673
|
errorBody = await response.text();
|
|
666
674
|
} catch {
|
|
667
675
|
}
|
|
668
|
-
throw new Error(
|
|
676
|
+
throw requestError(new Error(
|
|
669
677
|
`GHL API Error ${response.status} ${response.statusText}: ${method} ${path39}
|
|
670
678
|
${errorBody}`
|
|
671
|
-
);
|
|
679
|
+
));
|
|
680
|
+
}
|
|
681
|
+
let text;
|
|
682
|
+
try {
|
|
683
|
+
text = await response.text();
|
|
684
|
+
} catch (error) {
|
|
685
|
+
throw requestError(error);
|
|
672
686
|
}
|
|
673
|
-
const text = await response.text();
|
|
674
687
|
if (!text) return {};
|
|
675
688
|
try {
|
|
676
689
|
return JSON.parse(text);
|
|
@@ -15488,11 +15501,11 @@ function deriveBrandDna(raw, opts = {}) {
|
|
|
15488
15501
|
return m ? parseFloat(m[1]) : null;
|
|
15489
15502
|
};
|
|
15490
15503
|
const sizePx = raw.typography.sizes.map((s) => ({ v: px(s.value), count: s.count })).filter((s) => s.v !== null);
|
|
15491
|
-
const h1 = b?.h1Size ?? sizePx.filter((s) => s.v >= 32).sort((a,
|
|
15492
|
-
const bodySize = b?.bodySize ?? sizePx.filter((s) => s.v >= 14 && s.v <= 20).sort((a,
|
|
15504
|
+
const h1 = b?.h1Size ?? sizePx.filter((s) => s.v >= 32).sort((a, z84) => z84.v - a.v)[0]?.v;
|
|
15505
|
+
const bodySize = b?.bodySize ?? sizePx.filter((s) => s.v >= 14 && s.v <= 20).sort((a, z84) => z84.count - a.count)[0]?.v;
|
|
15493
15506
|
const sizes = { ...h1 && { h1 }, ...bodySize && { body: bodySize } };
|
|
15494
15507
|
const scaleRatio = h1 && bodySize ? Math.round(Math.pow(h1 / bodySize, 1 / 4) * 1e3) / 1e3 : void 0;
|
|
15495
|
-
const radiiPx = raw.radii.map((r) => px(r.value.split(/\s+/)[0])).filter((n) => n !== null && n < 100).sort((a,
|
|
15508
|
+
const radiiPx = raw.radii.map((r) => px(r.value.split(/\s+/)[0])).filter((n) => n !== null && n < 100).sort((a, z84) => a - z84);
|
|
15496
15509
|
const fcRadius = fc?.borderRadius ? px(fc.borderRadius) : null;
|
|
15497
15510
|
const distinct = [...new Set(radiiPx)];
|
|
15498
15511
|
const radius = fcRadius !== null ? { sm: Math.max(0, fcRadius - 2), md: fcRadius, lg: fcRadius * 2 } : distinct.length ? { sm: distinct[0], md: distinct[Math.floor(distinct.length / 2)], lg: distinct[distinct.length - 1] } : { sm: 4, md: 8, lg: 16 };
|
|
@@ -27038,6 +27051,37 @@ var init_client_preflight = __esm({
|
|
|
27038
27051
|
}
|
|
27039
27052
|
});
|
|
27040
27053
|
|
|
27054
|
+
// src/command-os/design/brief-validation.ts
|
|
27055
|
+
function designValidationMessage(error) {
|
|
27056
|
+
return [...new Set(error.issues.map((issue) => {
|
|
27057
|
+
const field = issue.path.join(".");
|
|
27058
|
+
if (field === "options.slides") return SLIDES_MESSAGE;
|
|
27059
|
+
if (field === "content.points" && issue.code === "too_big") return POINTS_MESSAGE;
|
|
27060
|
+
if (field.startsWith("content.points")) return "Enter each point as text.";
|
|
27061
|
+
if (field === "content.headline") return "Enter a headline.";
|
|
27062
|
+
return "Check the design details before trying again.";
|
|
27063
|
+
}))].join(" ");
|
|
27064
|
+
}
|
|
27065
|
+
var import_zod52, slideSchema, pointsSchema, slidesMin, slidesMax, pointsMax, DESIGN_FORM_LIMITS, SLIDES_MESSAGE, POINTS_MESSAGE;
|
|
27066
|
+
var init_brief_validation = __esm({
|
|
27067
|
+
"src/command-os/design/brief-validation.ts"() {
|
|
27068
|
+
"use strict";
|
|
27069
|
+
import_zod52 = require("zod");
|
|
27070
|
+
init_schema();
|
|
27071
|
+
slideSchema = DesignBriefSchema.shape.options.unwrap().shape.slides.unwrap();
|
|
27072
|
+
pointsSchema = DesignBriefSchema.shape.content.shape.points;
|
|
27073
|
+
slidesMin = slideSchema.minValue;
|
|
27074
|
+
slidesMax = slideSchema.maxValue;
|
|
27075
|
+
pointsMax = import_zod52.z.toJSONSchema(pointsSchema).maxItems;
|
|
27076
|
+
if (typeof slidesMin !== "number" || !Number.isFinite(slidesMin) || typeof slidesMax !== "number" || !Number.isFinite(slidesMax) || typeof pointsMax !== "number" || !Number.isFinite(pointsMax)) {
|
|
27077
|
+
throw new Error("The design schema must expose a minimum and maximum slide count and a maximum point count.");
|
|
27078
|
+
}
|
|
27079
|
+
DESIGN_FORM_LIMITS = { slidesMin, slidesMax, pointsMax };
|
|
27080
|
+
SLIDES_MESSAGE = `Choose a whole number of slides from ${DESIGN_FORM_LIMITS.slidesMin} to ${DESIGN_FORM_LIMITS.slidesMax}.`;
|
|
27081
|
+
POINTS_MESSAGE = `Use at most ${DESIGN_FORM_LIMITS.pointsMax} points.`;
|
|
27082
|
+
}
|
|
27083
|
+
});
|
|
27084
|
+
|
|
27041
27085
|
// src/command-os/design/run-jobs.ts
|
|
27042
27086
|
function validateRunRequest(input) {
|
|
27043
27087
|
const a = input ?? {};
|
|
@@ -27050,13 +27094,13 @@ function validateRunRequest(input) {
|
|
|
27050
27094
|
if (headline3.length < 3 || headline3.length > 300) return { ok: false, error: "headline must be between 3 and 300 characters." };
|
|
27051
27095
|
const points = Array.isArray(a.points) ? a.points.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
|
|
27052
27096
|
if (points.length === 0) return { ok: false, error: "points must be a non-empty list." };
|
|
27053
|
-
if (points.length >
|
|
27097
|
+
if (points.length > DESIGN_FORM_LIMITS.pointsMax) return { ok: false, error: POINTS_MESSAGE };
|
|
27054
27098
|
if (points.some((p) => p.includes("|"))) return { ok: false, error: "a point may not contain the | character; it separates them on the command line." };
|
|
27055
27099
|
if (points.some((p) => p.length > 400)) return { ok: false, error: "a point may be 400 characters at most." };
|
|
27056
27100
|
const model = str3("model");
|
|
27057
27101
|
if (model && !MODEL.test(model)) return { ok: false, error: "model must be a plain engine name." };
|
|
27058
|
-
const slides =
|
|
27059
|
-
if (slides !== void 0 && (slides <
|
|
27102
|
+
const slides = a.slides;
|
|
27103
|
+
if (slides !== void 0 && (typeof slides !== "number" || !Number.isInteger(slides) || slides < DESIGN_FORM_LIMITS.slidesMin || slides > DESIGN_FORM_LIMITS.slidesMax)) return { ok: false, error: SLIDES_MESSAGE };
|
|
27060
27104
|
let refusal = null;
|
|
27061
27105
|
const optional = (k, max = 400) => {
|
|
27062
27106
|
const v = str3(k);
|
|
@@ -27102,6 +27146,9 @@ function argvFor(r, outDir) {
|
|
|
27102
27146
|
return argv;
|
|
27103
27147
|
}
|
|
27104
27148
|
function startDesignRun(request, opts = {}) {
|
|
27149
|
+
const checked = validateRunRequest(request);
|
|
27150
|
+
if (!checked.ok) throw new Error(checked.error);
|
|
27151
|
+
request = checked.value;
|
|
27105
27152
|
const id = (0, import_node_crypto6.randomUUID)();
|
|
27106
27153
|
const outDir = (0, import_node_path5.join)(designRunsDir(), id);
|
|
27107
27154
|
(0, import_node_fs5.mkdirSync)(outDir, { recursive: true });
|
|
@@ -27179,6 +27226,7 @@ var init_run_jobs = __esm({
|
|
|
27179
27226
|
import_node_crypto6 = require("node:crypto");
|
|
27180
27227
|
init_credentials_store();
|
|
27181
27228
|
init_schema();
|
|
27229
|
+
init_brief_validation();
|
|
27182
27230
|
SLUG2 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
27183
27231
|
MODEL = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
27184
27232
|
JOINED_FLAG = /^--[a-z][a-z-]*=/;
|
|
@@ -28605,7 +28653,7 @@ var require_package = __commonJS({
|
|
|
28605
28653
|
"package.json"(exports2, module2) {
|
|
28606
28654
|
module2.exports = {
|
|
28607
28655
|
name: "@elitedcs/ghl-mcp",
|
|
28608
|
-
version: "3.83.
|
|
28656
|
+
version: "3.83.8",
|
|
28609
28657
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
28610
28658
|
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.",
|
|
28611
28659
|
main: "dist/index.js",
|
|
@@ -30520,6 +30568,16 @@ border-bottom:1px solid var(--rule);font-size:var(--fs-sm)}
|
|
|
30520
30568
|
</div>
|
|
30521
30569
|
|
|
30522
30570
|
<script>
|
|
30571
|
+
// Shared by the save schema and the standalone page (inlined by build-app.mjs).
|
|
30572
|
+
var CLIENT_FIELDS = {
|
|
30573
|
+
name: { max: 120, label: "Client name" },
|
|
30574
|
+
role: { max: 80, label: "Client role" },
|
|
30575
|
+
email: { max: 200, label: "Client email" },
|
|
30576
|
+
phone: { max: 40, label: "Client phone" },
|
|
30577
|
+
website: { max: 300, label: "Client website" },
|
|
30578
|
+
city: { max: 120, label: "Client city" }
|
|
30579
|
+
};
|
|
30580
|
+
|
|
30523
30581
|
/**
|
|
30524
30582
|
* The assessment engine \u2014 the ONE place the questions and the findings maths live.
|
|
30525
30583
|
*
|
|
@@ -31257,7 +31315,7 @@ function inputFor(q){
|
|
|
31257
31315
|
* assessment belongs to, so Save to GoHighLevel knows where to put it.
|
|
31258
31316
|
*/
|
|
31259
31317
|
function escH(s){return String(s==null?"":s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}
|
|
31260
|
-
function cf(k,ph,type){var c=S.client||{};return '<input class="a" type="'+(type||"text")+'" placeholder="'+escH(ph)+'" value="'+escH(c[k]||"")+'" oninput="setClient(\\''+k+'\\',this.value)">'}
|
|
31318
|
+
function cf(k,ph,type){var c=S.client||{};return '<input class="a" type="'+(type||"text")+'" maxlength="'+CLIENT_FIELDS[k].max+'" placeholder="'+escH(ph)+'" value="'+escH(c[k]||"")+'" oninput="setClient(\\''+k+'\\',this.value)">'}
|
|
31261
31319
|
function setClient(k,v){if(!S.client)S.client={};S.client[k]=v;save();head()}
|
|
31262
31320
|
function setSaveTo(v){S.saveTo=v;save();try{localStorage.setItem(K+":saveTo",v)}catch(e){}}
|
|
31263
31321
|
function accountSelectHtml(){
|
|
@@ -31365,7 +31423,7 @@ function openReport(){
|
|
|
31365
31423
|
}
|
|
31366
31424
|
function showSource(){
|
|
31367
31425
|
var text=reportHtml(), name=slug(S.biz)+"-report.html";
|
|
31368
|
-
|
|
31426
|
+
offerContents(savedMsg(saveFile(name,text,"text/html"),name), text);
|
|
31369
31427
|
}
|
|
31370
31428
|
function draw(){
|
|
31371
31429
|
head();
|
|
@@ -31396,6 +31454,17 @@ function dump(title,text){
|
|
|
31396
31454
|
var t=w.querySelector("textarea");t.value=text;t.focus();t.select();
|
|
31397
31455
|
w.scrollIntoView({behavior:"smooth",block:"nearest"});
|
|
31398
31456
|
}
|
|
31457
|
+
/** Offer a copy fallback without opening the file's source on a normal save. */
|
|
31458
|
+
function offerContents(message,text){
|
|
31459
|
+
var old=document.getElementById("dump");if(old)old.remove();
|
|
31460
|
+
var previous=document.getElementById("file-contents");if(previous)previous.remove();
|
|
31461
|
+
var box=document.createElement("div");box.id="file-contents";box.className="note";
|
|
31462
|
+
var note=document.createElement("p");note.textContent=message;box.appendChild(note);
|
|
31463
|
+
var button=document.createElement("button");button.type="button";button.className="mini";
|
|
31464
|
+
button.textContent="Show the file's contents";
|
|
31465
|
+
button.onclick=function(){dump("Select all, copy, and save the file's contents.",text)};
|
|
31466
|
+
box.appendChild(button);document.getElementById("body").appendChild(box);
|
|
31467
|
+
}
|
|
31399
31468
|
function slug(s){return String(s||"assessment").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"assessment"}
|
|
31400
31469
|
/**
|
|
31401
31470
|
* Write a real file to the operator's downloads.
|
|
@@ -31421,17 +31490,17 @@ function saveFile(name,text,type){
|
|
|
31421
31490
|
* simply refuses \u2014 which is exactly what some Chrome configurations do to a page
|
|
31422
31491
|
* opened from file://, the way this one ships. So a true return means "asked",
|
|
31423
31492
|
* never "saved", so announcing a completed save on the strength of it would be
|
|
31424
|
-
* a confident claim about something unobserved. The copy box is
|
|
31425
|
-
*
|
|
31493
|
+
* a confident claim about something unobserved. The copy box is offered behind an explicit control
|
|
31494
|
+
* and the wording is true under both outcomes.
|
|
31426
31495
|
*/
|
|
31427
31496
|
function savedMsg(ok,name){
|
|
31428
31497
|
return ok
|
|
31429
|
-
? "Asked your browser to save it as "+name+". If it did not appear in your downloads,
|
|
31430
|
-
: "Your browser would not save the file.
|
|
31498
|
+
? "Asked your browser to save it as "+name+". If it did not appear in your downloads, choose Show the file's contents to copy and save it as "+name+"."
|
|
31499
|
+
: "Your browser would not save the file. Choose Show the file's contents to copy and save it as "+name+".";
|
|
31431
31500
|
}
|
|
31432
31501
|
function dl(){
|
|
31433
31502
|
var text=JSON.stringify(S,null,2), name=slug(S.biz)+"-answers.json";
|
|
31434
|
-
|
|
31503
|
+
offerContents(savedMsg(saveFile(name,text,"application/json"),name), text);
|
|
31435
31504
|
}
|
|
31436
31505
|
function wipe(){if(!confirm)return;S.ans={};S.skip={};S.biz="";S.trade="";S.client={};S.tab="ask";S.area="client";save();draw()}
|
|
31437
31506
|
/** Save into the chosen account through Command OS on this computer: the same
|
|
@@ -32384,12 +32453,12 @@ ${d.voice.samples.length ? `<h2>Voice</h2><ul>${d.voice.samples.map((s) => `<li>
|
|
|
32384
32453
|
${opts.screenshot ? `<h2>Their site, as rendered</h2><img class="shot" src="/brand/${encodeURIComponent(d.slug)}/screenshot.png" alt="Screenshot of ${esc5(d.name)}'s website">` : ""}
|
|
32385
32454
|
<h2>Make something in this brand</h2>
|
|
32386
32455
|
<p class="small">Runs on <b>your own Claude</b>, on this computer, never our key. ${esc5(RUN_EXPECTATION)} The run keeps going if you close this tab. Write the headline and the points you want made, not instructions.</p>
|
|
32387
|
-
<form id="make"><div class="two">
|
|
32456
|
+
<form id="make" novalidate><div class="two">
|
|
32388
32457
|
<div><label for="mk-medium">What</label><select id="mk-medium"><option value="deck">A deck</option><option value="site">A landing page</option><option value="report">A report</option></select></div>
|
|
32389
|
-
<div><label for="mk-slides">Slides (a deck only)</label><input id="mk-slides" type="number" min="
|
|
32458
|
+
<div><label for="mk-slides">Slides (a deck only)</label><input id="mk-slides" type="number" min="${DESIGN_FORM_LIMITS.slidesMin}" max="${DESIGN_FORM_LIMITS.slidesMax}" step="1" value="6"></div>
|
|
32390
32459
|
</div>
|
|
32391
32460
|
<label for="mk-headline">Headline</label><input id="mk-headline" type="text" placeholder="The one sentence this has to land" autocomplete="off">
|
|
32392
|
-
<label for="mk-points">Points, one per line</label><input id="mk-points" type="text" placeholder="first point | second point | third point" autocomplete="off">
|
|
32461
|
+
<label for="mk-points">Points, one per line</label><input id="mk-points" data-max-points="${DESIGN_FORM_LIMITS.pointsMax}" type="text" placeholder="first point | second point | third point" autocomplete="off">
|
|
32393
32462
|
<div class="two">
|
|
32394
32463
|
<div><label for="mk-audience">Who it is for</label><input id="mk-audience" type="text" placeholder="clinic owners with two locations" autocomplete="off"></div>
|
|
32395
32464
|
<div><label for="mk-cta">Button label</label><input id="mk-cta" type="text" placeholder="Book the first visit" autocomplete="off"></div>
|
|
@@ -32430,6 +32499,9 @@ document.getElementById("make").addEventListener("submit",async function(e){
|
|
|
32430
32499
|
var g=function(id){return document.getElementById(id).value.trim()};
|
|
32431
32500
|
var pts=g("mk-points").split("|").map(function(x){return x.trim()}).filter(Boolean);
|
|
32432
32501
|
if(!g("mk-headline")||!pts.length){say("mk-status","A headline and at least one point, please.","err");return}
|
|
32502
|
+
if(pts.length>${DESIGN_FORM_LIMITS.pointsMax}){say("mk-status",${scriptJson(POINTS_MESSAGE)},"err");return}
|
|
32503
|
+
var slideCount=Number(g("mk-slides"));
|
|
32504
|
+
if(g("mk-medium")==="deck"&&(!Number.isInteger(slideCount)||slideCount<${DESIGN_FORM_LIMITS.slidesMin}||slideCount>${DESIGN_FORM_LIMITS.slidesMax})){say("mk-status",${scriptJson(SLIDES_MESSAGE)},"err");return}
|
|
32433
32505
|
var body={brand:${scriptJson(d.slug)},medium:g("mk-medium"),headline:g("mk-headline"),points:pts};
|
|
32434
32506
|
if(g("mk-audience"))body.audience=g("mk-audience");
|
|
32435
32507
|
if(g("mk-cta"))body.cta=g("mk-cta");
|
|
@@ -32442,7 +32514,7 @@ document.getElementById("make").addEventListener("submit",async function(e){
|
|
|
32442
32514
|
document.getElementById("mk-cancel").hidden=false;
|
|
32443
32515
|
started=Date.now();passes=0;tick();ticker=setInterval(tick,1000);
|
|
32444
32516
|
poll=setInterval(async function(){
|
|
32445
|
-
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId))).json();
|
|
32517
|
+
var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId),{headers:{"x-command-os-seat":KEY}})).json();
|
|
32446
32518
|
var lines=s.tail||[];
|
|
32447
32519
|
show(lines.join("\\n"));
|
|
32448
32520
|
passes=lines.filter(function(l){return l.trim().slice(0,5)==="pass "}).length;
|
|
@@ -32468,6 +32540,7 @@ var esc5, STYLE, SHELL_JS, foot;
|
|
|
32468
32540
|
var init_brand_page = __esm({
|
|
32469
32541
|
"src/command-os/design/brand-page.ts"() {
|
|
32470
32542
|
"use strict";
|
|
32543
|
+
init_brief_validation();
|
|
32471
32544
|
init_run_expectations();
|
|
32472
32545
|
init_fonts();
|
|
32473
32546
|
init_cockpit_shell();
|
|
@@ -32905,6 +32978,22 @@ var init_capability_runs = __esm({
|
|
|
32905
32978
|
}
|
|
32906
32979
|
});
|
|
32907
32980
|
|
|
32981
|
+
// src/command-os/assessment/client-fields.js
|
|
32982
|
+
var CLIENT_FIELDS;
|
|
32983
|
+
var init_client_fields = __esm({
|
|
32984
|
+
"src/command-os/assessment/client-fields.js"() {
|
|
32985
|
+
"use strict";
|
|
32986
|
+
CLIENT_FIELDS = {
|
|
32987
|
+
name: { max: 120, label: "Client name" },
|
|
32988
|
+
role: { max: 80, label: "Client role" },
|
|
32989
|
+
email: { max: 200, label: "Client email" },
|
|
32990
|
+
phone: { max: 40, label: "Client phone" },
|
|
32991
|
+
website: { max: 300, label: "Client website" },
|
|
32992
|
+
city: { max: 120, label: "Client city" }
|
|
32993
|
+
};
|
|
32994
|
+
}
|
|
32995
|
+
});
|
|
32996
|
+
|
|
32908
32997
|
// src/command-os/assessment/transcript.ts
|
|
32909
32998
|
function loose(s) {
|
|
32910
32999
|
return String(s ?? "").replace(/[‘’ʼ]/g, "'").replace(/[“”]/g, '"').replace(/[–—]/g, "-").replace(/\s+/g, " ").trim().toLowerCase();
|
|
@@ -33212,6 +33301,20 @@ var init_report = __esm({
|
|
|
33212
33301
|
});
|
|
33213
33302
|
|
|
33214
33303
|
// src/tools/assessment.ts
|
|
33304
|
+
function assessmentValidationMessage(error) {
|
|
33305
|
+
if (!(error instanceof import_zod53.z.ZodError)) return void 0;
|
|
33306
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33307
|
+
return error.issues.flatMap((issue) => {
|
|
33308
|
+
const key = issue.path.join(".");
|
|
33309
|
+
if (seen.has(key)) return [];
|
|
33310
|
+
seen.add(key);
|
|
33311
|
+
const field = issue.path[0] === "client" ? Object.entries(CLIENT_FIELDS).find(([name]) => name === issue.path[1])?.[1] : void 0;
|
|
33312
|
+
if (field && issue.code === "too_big") return [`${field.label} is too long, ${field.max} characters at most.`];
|
|
33313
|
+
if (field) return [`Enter ${field.label.toLowerCase()} as text.`];
|
|
33314
|
+
if (key === "biz") return ["Enter the business name before saving."];
|
|
33315
|
+
return ["Check the assessment answers before saving."];
|
|
33316
|
+
}).join(" ");
|
|
33317
|
+
}
|
|
33215
33318
|
function today() {
|
|
33216
33319
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
33217
33320
|
}
|
|
@@ -33240,7 +33343,7 @@ async function createPipeline(client, locationId2) {
|
|
|
33240
33343
|
});
|
|
33241
33344
|
return { ok: true };
|
|
33242
33345
|
} catch (e) {
|
|
33243
|
-
return { error: `could not create the "${PIPELINE_NAME}" pipeline: ${String(e)}
|
|
33346
|
+
return { error: `could not create the "${PIPELINE_NAME}" pipeline: ${String(e)}`, failure: { detail: (0, import_node_util3.inspect)(e) } };
|
|
33244
33347
|
}
|
|
33245
33348
|
}
|
|
33246
33349
|
async function findStage(client, locationId2, stage, mayCreate = false) {
|
|
@@ -33252,7 +33355,7 @@ async function findStage(client, locationId2, stage, mayCreate = false) {
|
|
|
33252
33355
|
try {
|
|
33253
33356
|
pipelines = await read2();
|
|
33254
33357
|
} catch (e) {
|
|
33255
|
-
return { error: `could not read pipelines: ${String(e)}
|
|
33358
|
+
return { error: `could not read pipelines: ${String(e)}`, failure: { detail: (0, import_node_util3.inspect)(e) } };
|
|
33256
33359
|
}
|
|
33257
33360
|
const named = (list2) => list2.find((p) => (p.name ?? "").trim().toLowerCase() === PIPELINE_NAME.toLowerCase());
|
|
33258
33361
|
let pipe = named(pipelines);
|
|
@@ -33262,7 +33365,7 @@ async function findStage(client, locationId2, stage, mayCreate = false) {
|
|
|
33262
33365
|
try {
|
|
33263
33366
|
pipe = named(await read2());
|
|
33264
33367
|
} catch (e) {
|
|
33265
|
-
return { error: `created the "${PIPELINE_NAME}" pipeline but could not read it back: ${String(e)}
|
|
33368
|
+
return { error: `created the "${PIPELINE_NAME}" pipeline but could not read it back: ${String(e)}`, failure: { detail: (0, import_node_util3.inspect)(e) } };
|
|
33266
33369
|
}
|
|
33267
33370
|
if (!pipe) return { error: `created the "${PIPELINE_NAME}" pipeline but it is not in the account when read back. Nothing was assumed; try saving again.` };
|
|
33268
33371
|
}
|
|
@@ -33275,7 +33378,7 @@ async function findStage(client, locationId2, stage, mayCreate = false) {
|
|
|
33275
33378
|
}
|
|
33276
33379
|
return { pipelineId: pipe.id, stageId: st.id, stageName: stage };
|
|
33277
33380
|
}
|
|
33278
|
-
async function saveAssessment(client, locationId2, args) {
|
|
33381
|
+
async function saveAssessment(client, locationId2, args, onOpportunityNoteFailure) {
|
|
33279
33382
|
const state = normalise(args.assessment);
|
|
33280
33383
|
const computed = computedFor(state);
|
|
33281
33384
|
const stage = stageFor({ verdict: computed.verdict, proposalSent: args.proposalSent, outcome: args.outcome });
|
|
@@ -33311,6 +33414,7 @@ async function saveAssessment(client, locationId2, args) {
|
|
|
33311
33414
|
let opportunityNote = null;
|
|
33312
33415
|
if ("error" in hit) {
|
|
33313
33416
|
opportunityNote = hit.error;
|
|
33417
|
+
if (hit.failure) onOpportunityNoteFailure?.(hit.failure);
|
|
33314
33418
|
} else {
|
|
33315
33419
|
const body = {
|
|
33316
33420
|
locationId: locationId2,
|
|
@@ -33362,11 +33466,11 @@ function registerAssessmentTools(server2, client) {
|
|
|
33362
33466
|
"attach_assessment",
|
|
33363
33467
|
"Attach a saved assessment to a client card, so the numbers from the sales conversation follow them into the intake and their build. Keep your own account active. Nothing is written to a GoHighLevel account. Use fill to save the overlapping intake answers, detach to remove the snapshot, or purchases to record what they bought.",
|
|
33364
33468
|
{
|
|
33365
|
-
clientLocationId:
|
|
33366
|
-
action:
|
|
33367
|
-
contactId:
|
|
33368
|
-
opportunityId:
|
|
33369
|
-
purchasedFeatureIds:
|
|
33469
|
+
clientLocationId: import_zod53.z.string().min(1).describe("The destination client card. This is not the account holding the saved assessment."),
|
|
33470
|
+
action: import_zod53.z.enum(["get", "attach", "fill", "detach", "purchases"]).default("attach"),
|
|
33471
|
+
contactId: import_zod53.z.string().optional().describe("The saved assessment contact from list_assessments in your active account."),
|
|
33472
|
+
opportunityId: import_zod53.z.string().optional(),
|
|
33473
|
+
purchasedFeatureIds: import_zod53.z.array(import_zod53.z.string()).optional().describe("Feature ids returned by get. Record only what the client bought. An empty list explicitly means none.")
|
|
33370
33474
|
},
|
|
33371
33475
|
async (args) => {
|
|
33372
33476
|
const [{ readCockpitState: readCockpitState2, writeCockpitState: writeCockpitState2 }, { TokenRegistry: TokenRegistry2 }, { composeQuestionSet: composeQuestionSet2 }, { readOverlay: readOverlay2 }] = await Promise.all([
|
|
@@ -33390,15 +33494,15 @@ function registerAssessmentTools(server2, client) {
|
|
|
33390
33494
|
"save_assessment",
|
|
33391
33495
|
"Save an assessment into YOUR OWN GoHighLevel: the prospect as a contact, the answer sheet as a note on that contact, and the deal as an opportunity in your Assessments pipeline. Nothing is stored anywhere else. Re-saving writes a new note rather than overwriting, so the history is kept and the newest is what loads back.",
|
|
33392
33496
|
{
|
|
33393
|
-
locationId:
|
|
33497
|
+
locationId: import_zod53.z.string().optional().describe("Your own GHL sub-account. Defaults to the active one."),
|
|
33394
33498
|
assessment: StateSchema,
|
|
33395
|
-
contactName:
|
|
33396
|
-
email:
|
|
33397
|
-
phone:
|
|
33398
|
-
proposalValue:
|
|
33399
|
-
outcome:
|
|
33400
|
-
proposalSent:
|
|
33401
|
-
createPipeline:
|
|
33499
|
+
contactName: import_zod53.z.string().optional().describe("The person you spoke to."),
|
|
33500
|
+
email: import_zod53.z.string().optional(),
|
|
33501
|
+
phone: import_zod53.z.string().optional(),
|
|
33502
|
+
proposalValue: import_zod53.z.number().optional().describe("What you quoted, if you have quoted. Used as the opportunity value instead of the annual figure."),
|
|
33503
|
+
outcome: import_zod53.z.enum(["won", "lost"]).optional().describe("Set only when the deal has actually closed."),
|
|
33504
|
+
proposalSent: import_zod53.z.boolean().optional(),
|
|
33505
|
+
createPipeline: import_zod53.z.boolean().optional().describe('Create the "Assessments" pipeline in your own GoHighLevel if it is not there yet. Off by default: it is a write to your account, so it is asked for rather than assumed. Say yes the first time and never again.')
|
|
33402
33506
|
},
|
|
33403
33507
|
async (args) => saveAssessment(client, client.resolveLocationId(args.locationId), args)
|
|
33404
33508
|
);
|
|
@@ -33407,7 +33511,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
33407
33511
|
"load_assessment",
|
|
33408
33512
|
"Load a saved assessment back out of your GoHighLevel, so someone else on your team can pick it up. Reads the notes on a contact and returns the most recent complete answer sheet. If a note has been edited or a piece is missing it returns nothing rather than a partial record.",
|
|
33409
33513
|
{
|
|
33410
|
-
contactId:
|
|
33514
|
+
contactId: import_zod53.z.string().describe("The contact the assessment was saved against.")
|
|
33411
33515
|
},
|
|
33412
33516
|
async ({ contactId }) => {
|
|
33413
33517
|
const { record: joined, notesRead } = await loadAssessmentRecord(client, contactId);
|
|
@@ -33437,8 +33541,8 @@ function registerAssessmentTools(server2, client) {
|
|
|
33437
33541
|
"list_assessments",
|
|
33438
33542
|
"List the assessments on your Assessments pipeline: who they are for, what stage each is at, and what the deal is worth. This is the board an agency owner checks, and the list a remote employee picks their next job from.",
|
|
33439
33543
|
{
|
|
33440
|
-
locationId:
|
|
33441
|
-
stage:
|
|
33544
|
+
locationId: import_zod53.z.string().optional().describe("Your own GHL sub-account. Defaults to the active one."),
|
|
33545
|
+
stage: import_zod53.z.enum(["Gathering", "Findings", "Proposal sent", "Won", "Lost"]).optional().describe("Only this stage.")
|
|
33442
33546
|
},
|
|
33443
33547
|
async ({ locationId: loc, stage }) => {
|
|
33444
33548
|
const locationId2 = client.resolveLocationId(loc);
|
|
@@ -33464,14 +33568,14 @@ function registerAssessmentTools(server2, client) {
|
|
|
33464
33568
|
"read_assessment_transcript",
|
|
33465
33569
|
"Turn a recorded conversation into assessment answers, WITHOUT letting anything into the record that the owner did not say. You read the transcript and propose answers; each one must carry the verbatim words it came from. This checks every quote actually appears in the transcript and throws out the ones that do not, and holds back anything you worked out rather than heard so the operator can confirm it. Use it after a coffee-shop meeting, a Zoom call or a Plaud recording.",
|
|
33466
33570
|
{
|
|
33467
|
-
transcript:
|
|
33468
|
-
proposed:
|
|
33469
|
-
questionId:
|
|
33470
|
-
value:
|
|
33471
|
-
quote:
|
|
33472
|
-
reasoning:
|
|
33571
|
+
transcript: import_zod53.z.string().min(20).describe("The recording, as text. Paste the whole thing."),
|
|
33572
|
+
proposed: import_zod53.z.array(import_zod53.z.object({
|
|
33573
|
+
questionId: import_zod53.z.string().describe("The assessment question this answers."),
|
|
33574
|
+
value: import_zod53.z.string().describe("What to store, in the unit the question wants."),
|
|
33575
|
+
quote: import_zod53.z.string().describe("The owner's own words, VERBATIM from the transcript. Do not tidy it up \u2014 if it is not in the transcript the answer is thrown out."),
|
|
33576
|
+
reasoning: import_zod53.z.string().optional().describe("How you got from the quote to the value, when they are not the same.")
|
|
33473
33577
|
})).describe("One entry per question you could answer. Leave out anything the conversation did not cover."),
|
|
33474
|
-
existing:
|
|
33578
|
+
existing: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.union([import_zod53.z.string(), import_zod53.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them.")
|
|
33475
33579
|
},
|
|
33476
33580
|
async ({ transcript, proposed, existing }) => {
|
|
33477
33581
|
const gated = gateExtraction(transcript, proposed, QUESTION_UNITS);
|
|
@@ -33497,15 +33601,15 @@ function registerAssessmentTools(server2, client) {
|
|
|
33497
33601
|
"Write the finished assessment report to a file on THIS machine, ready to hand to the prospect. The file is a complete, self-contained page: no fonts, scripts or images loaded from anywhere, so it opens on any host, in any browser, forever. It is written in the operator's branding and carries nothing about the software that produced it. We never publish it anywhere ourselves \u2014 the operator owns where it goes.",
|
|
33498
33602
|
{
|
|
33499
33603
|
assessment: StateSchema,
|
|
33500
|
-
agencyName:
|
|
33501
|
-
agencyContact:
|
|
33502
|
-
accent:
|
|
33503
|
-
services:
|
|
33504
|
-
name:
|
|
33505
|
-
price:
|
|
33506
|
-
covers:
|
|
33604
|
+
agencyName: import_zod53.z.string().optional().describe("Whose letterhead this goes out on. Read from your agency profile if you have one set."),
|
|
33605
|
+
agencyContact: import_zod53.z.string().optional().describe("Email or phone for the footer."),
|
|
33606
|
+
accent: import_zod53.z.string().optional().describe("Brand colour as a hex code, e.g. #8A2B2B."),
|
|
33607
|
+
services: import_zod53.z.array(import_zod53.z.object({
|
|
33608
|
+
name: import_zod53.z.string().describe("Your service, in the words you sell it in."),
|
|
33609
|
+
price: import_zod53.z.number().optional().describe("Your price. Leave it out and the report shows the plan without an investment section rather than inventing a number."),
|
|
33610
|
+
covers: import_zod53.z.array(import_zod53.z.string()).describe("Which findings it fixes: missed, waited, quotes, noshow.")
|
|
33507
33611
|
})).optional().describe("YOUR menu at YOUR prices. Never ours."),
|
|
33508
|
-
outDir:
|
|
33612
|
+
outDir: import_zod53.z.string().optional().describe("Where to write it. Defaults to ~/Documents/assessments.")
|
|
33509
33613
|
},
|
|
33510
33614
|
async (args) => {
|
|
33511
33615
|
const state = normalise(args.assessment);
|
|
@@ -33564,11 +33668,13 @@ function registerAssessmentTools(server2, client) {
|
|
|
33564
33668
|
}
|
|
33565
33669
|
);
|
|
33566
33670
|
}
|
|
33567
|
-
var
|
|
33671
|
+
var import_node_util3, import_zod53, import_node_crypto8, import_node_fs6, import_node_os, import_node_path6, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema;
|
|
33568
33672
|
var init_assessment = __esm({
|
|
33569
33673
|
"src/tools/assessment.ts"() {
|
|
33570
33674
|
"use strict";
|
|
33571
|
-
|
|
33675
|
+
import_node_util3 = require("node:util");
|
|
33676
|
+
import_zod53 = require("zod");
|
|
33677
|
+
init_client_fields();
|
|
33572
33678
|
init_attachment();
|
|
33573
33679
|
init_attachment_actions();
|
|
33574
33680
|
init_tool_helpers();
|
|
@@ -33591,13 +33697,13 @@ var init_assessment = __esm({
|
|
|
33591
33697
|
areaOrder: () => AREAS.map((a) => a[1]),
|
|
33592
33698
|
assumption: (id) => ASSUMPTION_LABEL.get(id)
|
|
33593
33699
|
};
|
|
33594
|
-
StateSchema =
|
|
33595
|
-
biz:
|
|
33596
|
-
trade:
|
|
33597
|
-
ans:
|
|
33598
|
-
skip:
|
|
33599
|
-
asm:
|
|
33600
|
-
client:
|
|
33700
|
+
StateSchema = import_zod53.z.object({
|
|
33701
|
+
biz: import_zod53.z.string().min(1).describe("The prospect's business name."),
|
|
33702
|
+
trade: import_zod53.z.string().optional().describe("Their trade. Decides which benchmarks may be applied."),
|
|
33703
|
+
ans: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.union([import_zod53.z.string(), import_zod53.z.number()])).describe("questionId -> answer, exactly as typed."),
|
|
33704
|
+
skip: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.boolean()).optional().describe("questionId -> true where they did not know."),
|
|
33705
|
+
asm: import_zod53.z.record(import_zod53.z.string(), import_zod53.z.number()).optional().describe("assumption id -> rate as a decimal."),
|
|
33706
|
+
client: import_zod53.z.object({ name: import_zod53.z.string().max(CLIENT_FIELDS.name.max), role: import_zod53.z.string().max(CLIENT_FIELDS.role.max), email: import_zod53.z.string().max(CLIENT_FIELDS.email.max), phone: import_zod53.z.string().max(CLIENT_FIELDS.phone.max), website: import_zod53.z.string().max(CLIENT_FIELDS.website.max), city: import_zod53.z.string().max(CLIENT_FIELDS.city.max) }).partial().optional().describe("The person and place this is for, as typed on The client step. Rides with the answers so a load brings it back.")
|
|
33601
33707
|
});
|
|
33602
33708
|
}
|
|
33603
33709
|
});
|
|
@@ -33985,9 +34091,12 @@ __export(dashboard_exports, {
|
|
|
33985
34091
|
insideDir: () => insideDir,
|
|
33986
34092
|
intakeInput: () => intakeInput,
|
|
33987
34093
|
intakeRequestError: () => intakeRequestError,
|
|
34094
|
+
isAllowedCockpitHost: () => isAllowedCockpitHost,
|
|
33988
34095
|
isLoopback: () => isLoopback,
|
|
34096
|
+
isUpstreamFailure: () => isUpstreamFailure,
|
|
33989
34097
|
launcherAlreadyOffered: () => launcherAlreadyOffered,
|
|
33990
34098
|
listClientDocs: () => listClientDocs,
|
|
34099
|
+
mayServeSeat: () => mayServeSeat,
|
|
33991
34100
|
memberFacingError: () => memberFacingError,
|
|
33992
34101
|
newClientFailure: () => newClientFailure,
|
|
33993
34102
|
normalizedIntake: () => normalizedIntake,
|
|
@@ -34457,19 +34566,34 @@ function memberFacingError(e, own = []) {
|
|
|
34457
34566
|
const raw = e instanceof Error ? e.message : String(e);
|
|
34458
34567
|
return own.includes(raw) ? raw : readFailureText(e);
|
|
34459
34568
|
}
|
|
34460
|
-
function
|
|
34569
|
+
function isUpstreamFailure(error) {
|
|
34570
|
+
if (typeof error === "string") return /^\s*(?:\{\s*"|\[\s*\{)/.test(error);
|
|
34571
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34572
|
+
let current = error;
|
|
34573
|
+
while (current !== null && typeof current === "object" && !seen.has(current)) {
|
|
34574
|
+
seen.add(current);
|
|
34575
|
+
if (isGHLRequestError(current) || current instanceof TypeError || current instanceof DOMException) return true;
|
|
34576
|
+
if ("name" in current && (current.name === "AbortError" || current.name === "TimeoutError")) return true;
|
|
34577
|
+
if ("code" in current && typeof current.code === "string" && /^(?:E[A-Z0-9_]+|UND_ERR_[A-Z0-9_]+)$/.test(current.code)) return true;
|
|
34578
|
+
current = "cause" in current ? current.cause : void 0;
|
|
34579
|
+
}
|
|
34580
|
+
return false;
|
|
34581
|
+
}
|
|
34582
|
+
function newClientFailure(error, surface = "command-os", account2) {
|
|
34461
34583
|
const connect2 = surface === "command-os" ? "paste its key into Command OS" : "register it in GHL Command with its key";
|
|
34462
|
-
const refusal = subAccountRefusal(error, "create", surface);
|
|
34584
|
+
const refusal = account2 ? void 0 : subAccountRefusal(error, "create", surface);
|
|
34463
34585
|
if (refusal) return refusal;
|
|
34464
34586
|
const raw = error instanceof Error ? error.message : String(error);
|
|
34465
|
-
if (
|
|
34466
|
-
if (raw ===
|
|
34467
|
-
if (raw === "
|
|
34468
|
-
if (raw
|
|
34469
|
-
if (raw.startsWith("
|
|
34470
|
-
if (
|
|
34471
|
-
if (/GHL API Error
|
|
34472
|
-
if (/GHL API Error
|
|
34587
|
+
if (account2 && !account2.fromCaughtError && !isUpstreamFailure(error)) return raw;
|
|
34588
|
+
if (raw === CREATE_PLAN_REFUSAL && !account2) return surface === "command-os" ? raw : "Create it in GoHighLevel and register it in GHL Command with its key.";
|
|
34589
|
+
if (!account2 && (raw === "Choose the client industry." || raw === "Connect your agency in Your agency before creating a sub-account.")) return raw;
|
|
34590
|
+
if (!account2 && raw === "business name is required") return "Enter the business name, then create the sub-account.";
|
|
34591
|
+
if (!account2 && raw.startsWith("No agency key registered.")) return `No agency key is registered; add your agency Private Integration key to let GHL Command create sub-accounts on GoHighLevel's Agency Pro plan, or on other plans create it in GoHighLevel and ${connect2}.`;
|
|
34592
|
+
if (!account2 && raw.startsWith("Couldn't resolve the agency company ID")) return "The agency company ID is missing; check the agency registered in GHL Command before creating the sub-account.";
|
|
34593
|
+
if (/GHL API Error 401/.test(raw)) return account2 ? `${account2.failed}: the key for this account was refused. Check the account key and its permissions in GoHighLevel. ${account2.next}` : "GoHighLevel refused the agency key; check whether the agency Private Integration has expired or been revoked before trying again.";
|
|
34594
|
+
if (/GHL API Error 403/.test(raw)) return account2 ? `${account2.failed}: the key for this account was refused. Check the account key and its permissions in GoHighLevel. ${account2.next}` : "GoHighLevel refused access for this agency; check the agency Private Integration permissions and plan in GoHighLevel before trying again.";
|
|
34595
|
+
if (/GHL API Error 422/.test(raw)) return account2 ? `${account2.failed}: GoHighLevel rejected the details. Check the entered details in this account. ${account2.next}` : "GoHighLevel rejected the sub-account details; check the business and owner details before trying again.";
|
|
34596
|
+
if (account2) return `${account2.failed}. ${account2.next}`;
|
|
34473
34597
|
return `GHL Command could not confirm whether the sub-account was created; check GoHighLevel before trying again to avoid creating a duplicate, and ${connect2} if it exists.`;
|
|
34474
34598
|
}
|
|
34475
34599
|
function insideDir(dir, p) {
|
|
@@ -35752,22 +35876,45 @@ function checkSeatToken(req, expected) {
|
|
|
35752
35876
|
}
|
|
35753
35877
|
return null;
|
|
35754
35878
|
}
|
|
35755
|
-
function
|
|
35756
|
-
const
|
|
35757
|
-
|
|
35758
|
-
|
|
35759
|
-
|
|
35760
|
-
|
|
35761
|
-
}
|
|
35762
|
-
|
|
35763
|
-
|
|
35764
|
-
|
|
35879
|
+
function cockpitHeader(req, name) {
|
|
35880
|
+
const value = req.headers[name];
|
|
35881
|
+
return Array.isArray(value) ? value.join(",") : value ?? "";
|
|
35882
|
+
}
|
|
35883
|
+
function isAllowedCockpitHost(req, port) {
|
|
35884
|
+
const host = cockpitHeader(req, "host").toLowerCase();
|
|
35885
|
+
return host === `127.0.0.1:${port}` || host === `localhost:${port}`;
|
|
35886
|
+
}
|
|
35887
|
+
function mayServeSeat(req, port) {
|
|
35888
|
+
return isLoopback(req) && isAllowedCockpitHost(req, port);
|
|
35889
|
+
}
|
|
35890
|
+
function cockpitPath(req) {
|
|
35891
|
+
return (req.url || "/").split("?")[0];
|
|
35892
|
+
}
|
|
35893
|
+
function checkCockpitPath(req) {
|
|
35894
|
+
const pathname = cockpitPath(req);
|
|
35895
|
+
if (!pathname.startsWith("/") || /\/\/|\\|#|[\x00-\x20\x7f]|%(?:2e|2f|5c)|%(?![0-9a-f]{2})/i.test(pathname) || pathname.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
35896
|
+
return { status: 400, error: "Use a canonical cockpit path." };
|
|
35765
35897
|
}
|
|
35766
|
-
|
|
35767
|
-
|
|
35898
|
+
return null;
|
|
35899
|
+
}
|
|
35900
|
+
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}`) {
|
|
35768
35903
|
return { status: 403, error: "cross-origin request rejected" };
|
|
35769
35904
|
}
|
|
35770
|
-
|
|
35905
|
+
return null;
|
|
35906
|
+
}
|
|
35907
|
+
function checkMutationGuard(req, port) {
|
|
35908
|
+
if (!isAllowedCockpitHost(req, port)) {
|
|
35909
|
+
return { status: 403, error: "This Host is not allowed to access the cockpit." };
|
|
35910
|
+
}
|
|
35911
|
+
const pathDenied = checkCockpitPath(req);
|
|
35912
|
+
if (pathDenied) return pathDenied;
|
|
35913
|
+
const method = (req.method || "GET").toUpperCase();
|
|
35914
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return null;
|
|
35915
|
+
const originDenied = checkCockpitOrigin(req, port);
|
|
35916
|
+
if (originDenied) return originDenied;
|
|
35917
|
+
if (cockpitHeader(req, "content-type").split(";")[0].trim().toLowerCase() !== "application/json") {
|
|
35771
35918
|
return { status: 415, error: "expected application/json" };
|
|
35772
35919
|
}
|
|
35773
35920
|
return null;
|
|
@@ -35775,8 +35922,13 @@ function checkMutationGuard(req, port) {
|
|
|
35775
35922
|
function cockpitGate(req, seatToken, port) {
|
|
35776
35923
|
const denied = checkMutationGuard(req, port) ?? checkSeatToken(req, seatToken);
|
|
35777
35924
|
if (denied) return denied;
|
|
35778
|
-
const pathname = (req
|
|
35779
|
-
if (SEAT_GUARDED_READS.includes(pathname)
|
|
35925
|
+
const pathname = cockpitPath(req);
|
|
35926
|
+
if (SEAT_GUARDED_READS.includes(pathname) || pathname === "/api/design" || pathname.startsWith("/api/design/")) {
|
|
35927
|
+
if (cockpitHeader(req, "sec-fetch-site").toLowerCase() === "cross-site") {
|
|
35928
|
+
return { status: 403, error: "cross-site request rejected" };
|
|
35929
|
+
}
|
|
35930
|
+
return checkCockpitOrigin(req, port) ?? checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
35931
|
+
}
|
|
35780
35932
|
return null;
|
|
35781
35933
|
}
|
|
35782
35934
|
function guarded(seatToken, port, routes) {
|
|
@@ -35901,7 +36053,7 @@ async function runDashboard(argv) {
|
|
|
35901
36053
|
questions: (id) => composeQuestionSet({ industry: readCockpitState().accounts?.[id]?.industry, agency: readOverlay() })
|
|
35902
36054
|
};
|
|
35903
36055
|
const server2 = http.createServer(guarded(seatToken, port, (req, res) => {
|
|
35904
|
-
const url = req
|
|
36056
|
+
const url = cockpitPath(req);
|
|
35905
36057
|
if (req.method === "POST" && url === "/api/client-assessment") {
|
|
35906
36058
|
readBody(req, res, 2e4, async (body) => {
|
|
35907
36059
|
try {
|
|
@@ -35910,7 +36062,11 @@ async function runDashboard(argv) {
|
|
|
35910
36062
|
res.end(JSON.stringify(result));
|
|
35911
36063
|
} catch (e) {
|
|
35912
36064
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
35913
|
-
|
|
36065
|
+
logQuietly(`client assessment: ${(0, import_node_util4.inspect)(e)}`);
|
|
36066
|
+
res.end(JSON.stringify({ ok: false, error: newClientFailure(e, "command-os", {
|
|
36067
|
+
failed: "The assessment could not be loaded or attached",
|
|
36068
|
+
next: "Check the selected account and refresh assessments before attaching again."
|
|
36069
|
+
}) }));
|
|
35914
36070
|
}
|
|
35915
36071
|
});
|
|
35916
36072
|
return;
|
|
@@ -35923,10 +36079,10 @@ async function runDashboard(argv) {
|
|
|
35923
36079
|
return;
|
|
35924
36080
|
}
|
|
35925
36081
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
35926
|
-
res.end(html(currentRoster(),
|
|
36082
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", "for", cap));
|
|
35927
36083
|
return;
|
|
35928
36084
|
}
|
|
35929
|
-
if (req.method === "GET" && url
|
|
36085
|
+
if (req.method === "GET" && url === "/api/capability") {
|
|
35930
36086
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
35931
36087
|
const cap = capabilityByKey(u.searchParams.get("key") ?? "");
|
|
35932
36088
|
const locationId2 = u.searchParams.get("locationId") ?? "";
|
|
@@ -36046,22 +36202,38 @@ async function runDashboard(argv) {
|
|
|
36046
36202
|
const c = clientFor(locationId2);
|
|
36047
36203
|
if (!c) throw new Error("No key on file for that account. Register it in GHL Command first.");
|
|
36048
36204
|
const assessment = StateSchema.parse(args.assessment);
|
|
36205
|
+
let opportunityNoteFailure;
|
|
36049
36206
|
const out = await saveAssessment(c, locationId2, {
|
|
36050
36207
|
assessment,
|
|
36051
36208
|
contactName: args.contactName || void 0,
|
|
36052
36209
|
email: args.email || void 0,
|
|
36053
36210
|
phone: args.phone || void 0,
|
|
36054
36211
|
createPipeline: args.createPipeline === true
|
|
36212
|
+
}, (failure) => {
|
|
36213
|
+
opportunityNoteFailure = failure;
|
|
36055
36214
|
});
|
|
36215
|
+
if (out.opportunityNote && (opportunityNoteFailure || isUpstreamFailure(out.opportunityNote))) {
|
|
36216
|
+
logQuietly(`assessment pipeline: ${out.opportunityNote}
|
|
36217
|
+
${opportunityNoteFailure?.detail ?? ""}`);
|
|
36218
|
+
out.opportunityNote = newClientFailure(out.opportunityNote, "command-os", {
|
|
36219
|
+
fromCaughtError: Boolean(opportunityNoteFailure),
|
|
36220
|
+
failed: "The answers were saved, but the Assessments pipeline could not be checked",
|
|
36221
|
+
next: "Check the Assessments pipeline in GoHighLevel before saving again."
|
|
36222
|
+
});
|
|
36223
|
+
}
|
|
36056
36224
|
res.end(JSON.stringify({ ok: true, ...out }));
|
|
36057
36225
|
} catch (e) {
|
|
36058
|
-
|
|
36226
|
+
logQuietly(`assessment save: ${(0, import_node_util4.inspect)(e)}`);
|
|
36227
|
+
res.end(JSON.stringify({ ok: false, error: assessmentValidationMessage(e) ?? newClientFailure(e, "command-os", {
|
|
36228
|
+
failed: "The assessment save could not be confirmed",
|
|
36229
|
+
next: "Check the contact and its notes in GoHighLevel before saving again."
|
|
36230
|
+
}) }));
|
|
36059
36231
|
}
|
|
36060
36232
|
})();
|
|
36061
36233
|
});
|
|
36062
36234
|
return;
|
|
36063
36235
|
}
|
|
36064
|
-
if (req.method === "GET" && url
|
|
36236
|
+
if (req.method === "GET" && url === "/api/intake-documents") {
|
|
36065
36237
|
const u = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
36066
36238
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36067
36239
|
if (seat) {
|
|
@@ -36109,7 +36281,7 @@ async function runDashboard(argv) {
|
|
|
36109
36281
|
const boardScreen = BOARD_ROUTES[url];
|
|
36110
36282
|
if (req.method === "GET" && boardScreen) {
|
|
36111
36283
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36112
|
-
res.end(html(currentRoster(),
|
|
36284
|
+
res.end(html(currentRoster(), mayServeSeat(req, port) ? seatToken : "", boardScreen));
|
|
36113
36285
|
return;
|
|
36114
36286
|
}
|
|
36115
36287
|
if ((req.method === "GET" || req.method === "HEAD") && (url === "/assessment" || url === "/assessment.html")) {
|
|
@@ -36121,7 +36293,7 @@ async function runDashboard(argv) {
|
|
|
36121
36293
|
"X-Robots-Tag": "noindex, nofollow"
|
|
36122
36294
|
});
|
|
36123
36295
|
const roster2 = currentRoster().map((r) => ({ locationId: r.locationId, name: r.name }));
|
|
36124
|
-
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(
|
|
36296
|
+
const clientsTag = `<script>window.COMMAND_OS_CLIENTS=${scriptJson(roster2)};window.COMMAND_OS_SEAT=${scriptJson(mayServeSeat(req, port) ? seatToken : "")};</script>`;
|
|
36125
36297
|
res.end(req.method === "HEAD" ? void 0 : withCockpitShell(ASSESSMENT_HTML, "assessment", assessmentAgencyTag() + clientsTag));
|
|
36126
36298
|
return;
|
|
36127
36299
|
}
|
|
@@ -36164,14 +36336,14 @@ async function runDashboard(argv) {
|
|
|
36164
36336
|
});
|
|
36165
36337
|
return;
|
|
36166
36338
|
}
|
|
36167
|
-
if (req.method === "GET" && url
|
|
36339
|
+
if (req.method === "GET" && url === "/api/client-preflight") {
|
|
36168
36340
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36169
36341
|
if (seat) {
|
|
36170
36342
|
res.writeHead(seat.status);
|
|
36171
36343
|
res.end(JSON.stringify({ ok: false, error: seat.error }));
|
|
36172
36344
|
return;
|
|
36173
36345
|
}
|
|
36174
|
-
const id = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36346
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36175
36347
|
if (!readableAccount(id)) {
|
|
36176
36348
|
res.writeHead(400);
|
|
36177
36349
|
res.end(JSON.stringify({ ok: false, error: "Choose a registered client." }));
|
|
@@ -36327,8 +36499,8 @@ async function runDashboard(argv) {
|
|
|
36327
36499
|
});
|
|
36328
36500
|
return;
|
|
36329
36501
|
}
|
|
36330
|
-
if (req.method === "GET" && url
|
|
36331
|
-
const industry = new URL(url, "http://x").searchParams.get("industry") || void 0;
|
|
36502
|
+
if (req.method === "GET" && url === "/api/intake") {
|
|
36503
|
+
const industry = new URL(req.url || "/", "http://x").searchParams.get("industry") || void 0;
|
|
36332
36504
|
const agency = readOverlay();
|
|
36333
36505
|
const composed = composeQuestionSet({ industry, agency });
|
|
36334
36506
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -36357,10 +36529,10 @@ async function runDashboard(argv) {
|
|
|
36357
36529
|
});
|
|
36358
36530
|
return;
|
|
36359
36531
|
}
|
|
36360
|
-
if (req.method === "GET" && url
|
|
36532
|
+
if (req.method === "GET" && url === "/api/client-intake") {
|
|
36361
36533
|
(async () => {
|
|
36362
36534
|
try {
|
|
36363
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36535
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36364
36536
|
const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
|
|
36365
36537
|
if (seat) {
|
|
36366
36538
|
res.writeHead(seat.status, { "Content-Type": "application/json" });
|
|
@@ -36384,7 +36556,7 @@ async function runDashboard(argv) {
|
|
|
36384
36556
|
res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q2) => q2.required && !String(answers[q2.label] ?? answers[q2.key] ?? "").trim()).map((q2) => q2.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
|
|
36385
36557
|
} catch (e) {
|
|
36386
36558
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36387
|
-
logQuietly(`client-intake ${new URL(url, "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36559
|
+
logQuietly(`client-intake ${new URL(req.url || "/", "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
36388
36560
|
res.end(JSON.stringify({ ok: false, error: intakeRequestError(e) }));
|
|
36389
36561
|
}
|
|
36390
36562
|
})();
|
|
@@ -36508,12 +36680,12 @@ async function runDashboard(argv) {
|
|
|
36508
36680
|
if (!rows.some((r) => r.locationId === locationId2)) rows.push({ locationId: locationId2, name: e.clientName || "(no account connected)", engagement: e });
|
|
36509
36681
|
}
|
|
36510
36682
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
36511
|
-
res.end(agencyProfilePageHtml(readAgencyProfile(), rows,
|
|
36683
|
+
res.end(agencyProfilePageHtml(readAgencyProfile(), rows, mayServeSeat(req, port) ? seatToken : ""));
|
|
36512
36684
|
return;
|
|
36513
36685
|
}
|
|
36514
36686
|
if (req.method === "GET" && url === "/brands") {
|
|
36515
36687
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36516
|
-
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(),
|
|
36688
|
+
res.end(brandsPageHtml(listBrands(), houseLooks(), listReferenceLooks(), mayServeSeat(req, port) ? seatToken : ""));
|
|
36517
36689
|
return;
|
|
36518
36690
|
}
|
|
36519
36691
|
if (req.method === "GET" && /^\/brand\/[a-z0-9-]+\/screenshot\.png$/.test(url)) {
|
|
@@ -36534,7 +36706,7 @@ async function runDashboard(argv) {
|
|
|
36534
36706
|
const dna = houseLooks().find((d) => d.slug === slug3) ?? readBrand(slug3);
|
|
36535
36707
|
const dir = brandExists(slug3) ? brandDir(slug3) : void 0;
|
|
36536
36708
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
36537
|
-
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken:
|
|
36709
|
+
res.end(brandCardHtml(dna, { screenshot: dir ? fs27.existsSync(path26.join(dir, "screenshot.png")) : false, dir, seatToken: mayServeSeat(req, port) ? seatToken : "" }));
|
|
36538
36710
|
} catch (e) {
|
|
36539
36711
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
36540
36712
|
res.end(e.message);
|
|
@@ -36560,8 +36732,8 @@ async function runDashboard(argv) {
|
|
|
36560
36732
|
});
|
|
36561
36733
|
return;
|
|
36562
36734
|
}
|
|
36563
|
-
if (req.method === "GET" && url
|
|
36564
|
-
const id = new URL(url, "http://x").searchParams.get("id");
|
|
36735
|
+
if (req.method === "GET" && url === "/api/design/run") {
|
|
36736
|
+
const id = new URL(req.url || "/", "http://x").searchParams.get("id");
|
|
36565
36737
|
const payload = id ? designRunStatus(id) : { runs: listDesignRuns() };
|
|
36566
36738
|
res.writeHead(payload ? 200 : 404, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
36567
36739
|
res.end(JSON.stringify(payload ?? { ok: false, error: "no such run" }));
|
|
@@ -36687,8 +36859,8 @@ async function runDashboard(argv) {
|
|
|
36687
36859
|
});
|
|
36688
36860
|
return;
|
|
36689
36861
|
}
|
|
36690
|
-
if (req.method === "GET" && url
|
|
36691
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36862
|
+
if (req.method === "GET" && url === "/handoff") {
|
|
36863
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36692
36864
|
if (!readableAccount(locationId2)) {
|
|
36693
36865
|
res.writeHead(400);
|
|
36694
36866
|
res.end("Choose a registered client.");
|
|
@@ -36786,13 +36958,17 @@ async function runDashboard(argv) {
|
|
|
36786
36958
|
}
|
|
36787
36959
|
} catch (e) {
|
|
36788
36960
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
36789
|
-
|
|
36961
|
+
logQuietly(`handoff email: ${(0, import_node_util4.inspect)(e)}`);
|
|
36962
|
+
res.end(JSON.stringify({ ok: false, error: newClientFailure(e, "command-os", {
|
|
36963
|
+
failed: url === "/api/handoff-prepare" ? "The handoff email could not be prepared" : "The handoff email send could not be confirmed",
|
|
36964
|
+
next: "Check Conversations in GoHighLevel before trying again."
|
|
36965
|
+
}) }));
|
|
36790
36966
|
}
|
|
36791
36967
|
});
|
|
36792
36968
|
return;
|
|
36793
36969
|
}
|
|
36794
|
-
if (req.method === "GET" && url
|
|
36795
|
-
const q2 = new URL(url, "http://x").searchParams;
|
|
36970
|
+
if (req.method === "GET" && url === "/blueprint") {
|
|
36971
|
+
const q2 = new URL(req.url || "/", "http://x").searchParams;
|
|
36796
36972
|
const locationId2 = q2.get("locationId") || "";
|
|
36797
36973
|
const st = readCockpitState();
|
|
36798
36974
|
const review = st.reviews?.[locationId2];
|
|
@@ -36806,8 +36982,8 @@ async function runDashboard(argv) {
|
|
|
36806
36982
|
res.end(reviewDocumentHtml(review, { assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: st.assessmentPurchases?.[locationId2], planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q2.get("agency") || st.agencyName, accent: q2.get("accent") || st.agencyAccent }));
|
|
36807
36983
|
return;
|
|
36808
36984
|
}
|
|
36809
|
-
if (req.method === "GET" && url
|
|
36810
|
-
const locationId2 = new URL(url, "http://x").searchParams.get("locationId") || "";
|
|
36985
|
+
if (req.method === "GET" && url === "/api/review") {
|
|
36986
|
+
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
36811
36987
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36812
36988
|
res.end(JSON.stringify(readCockpitState().reviews?.[locationId2] ?? { ok: false, error: "not generated yet" }));
|
|
36813
36989
|
return;
|
|
@@ -37281,10 +37457,11 @@ async function runDashboard(argv) {
|
|
|
37281
37457
|
process.on("SIGTERM", stop);
|
|
37282
37458
|
});
|
|
37283
37459
|
}
|
|
37284
|
-
var 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;
|
|
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;
|
|
37285
37461
|
var init_dashboard = __esm({
|
|
37286
37462
|
"src/dashboard.ts"() {
|
|
37287
37463
|
"use strict";
|
|
37464
|
+
import_node_util4 = require("node:util");
|
|
37288
37465
|
init_verify_readback();
|
|
37289
37466
|
init_intake_notification();
|
|
37290
37467
|
init_approved_status_writer();
|
|
@@ -37399,7 +37576,7 @@ var init_dashboard = __esm({
|
|
|
37399
37576
|
anthropicKeyWrites = Promise.resolve();
|
|
37400
37577
|
SEAT_TOKEN_HEADER = "x-command-os-seat";
|
|
37401
37578
|
NOT_SIGNED_IN_MSG = "This browser isn't signed in to Command OS on this computer. Open Command OS on the machine it runs on, and use the page it opens for you.";
|
|
37402
|
-
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots"];
|
|
37579
|
+
SEAT_GUARDED_READS = ["/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight"];
|
|
37403
37580
|
}
|
|
37404
37581
|
});
|
|
37405
37582
|
|
|
@@ -37522,7 +37699,7 @@ Token registry: ${registeredCount} location(s) registered${versionLine}`
|
|
|
37522
37699
|
"switch_location",
|
|
37523
37700
|
"Switch the active GHL sub-account. Automatically swaps the API key from the token registry if available. After switching, all tools default to the new location.",
|
|
37524
37701
|
{
|
|
37525
|
-
locationId:
|
|
37702
|
+
locationId: import_zod54.z.string().describe("The Location ID to switch to.")
|
|
37526
37703
|
},
|
|
37527
37704
|
async ({ locationId: locationId2 }) => withSwitchLock(async () => {
|
|
37528
37705
|
const previousId = client.defaultLocationId;
|
|
@@ -37598,9 +37775,9 @@ Still on: ${previousId || "none"}${hint}` }],
|
|
|
37598
37775
|
"register_location",
|
|
37599
37776
|
"Add a GHL sub-account to the token registry so switch_location can automatically use its API key. Each sub-account needs its own Private Integration key created in GHL Settings > Integrations.",
|
|
37600
37777
|
{
|
|
37601
|
-
locationId:
|
|
37602
|
-
name:
|
|
37603
|
-
apiKey:
|
|
37778
|
+
locationId: import_zod54.z.string().describe("The GHL Location ID (from Settings > Business Profile)."),
|
|
37779
|
+
name: import_zod54.z.string().describe("A friendly name for this sub-account (e.g. 'PNTracker', 'Med Spa Template')."),
|
|
37780
|
+
apiKey: import_zod54.z.string().describe("The Private Integration API key for this sub-account (starts with 'pit-').")
|
|
37604
37781
|
},
|
|
37605
37782
|
async ({ locationId: locationId2, name, apiKey: apiKey2 }) => {
|
|
37606
37783
|
const result = await registerVerifiedLocation(registry2, locationId2, apiKey2);
|
|
@@ -37622,7 +37799,7 @@ You can now use switch_location to switch to this sub-account.`
|
|
|
37622
37799
|
"register_agency_key",
|
|
37623
37800
|
"Store the AGENCY-level (company-scoped) API key in the token registry. This key powers agency-wide tools: list_snapshots, create_snapshot_share_link, and list_available_locations across all sub-accounts. Create it at the AGENCY level in GHL (Agency Settings > Private Integrations) \u2014 it is different from a sub-account's key. The key is validated before saving.",
|
|
37624
37801
|
{
|
|
37625
|
-
apiKey:
|
|
37802
|
+
apiKey: import_zod54.z.string().describe("The agency-level Private Integration API key (starts with 'pit-'). Must be created in AGENCY settings, not inside a sub-account.")
|
|
37626
37803
|
},
|
|
37627
37804
|
async ({ apiKey: apiKey2 }) => {
|
|
37628
37805
|
if (!registry2) {
|
|
@@ -37676,7 +37853,7 @@ Agency-wide tools now available: list_snapshots, create_snapshot_share_link, and
|
|
|
37676
37853
|
"unregister_location",
|
|
37677
37854
|
"Remove a GHL sub-account from the token registry.",
|
|
37678
37855
|
{
|
|
37679
|
-
locationId:
|
|
37856
|
+
locationId: import_zod54.z.string().describe("The Location ID to remove.")
|
|
37680
37857
|
},
|
|
37681
37858
|
async ({ locationId: locationId2 }) => {
|
|
37682
37859
|
if (!registry2) {
|
|
@@ -37702,12 +37879,12 @@ Agency-wide tools now available: list_snapshots, create_snapshot_share_link, and
|
|
|
37702
37879
|
"register_company_firebase",
|
|
37703
37880
|
"Register a GHL company's Firebase credentials so the workflow builder and all Firebase-gated tools work when you switch into THAT company's sub-accounts. Firebase refresh tokens are company-scoped, so managing a client's GHL (e.g. an account where you're an admin user) requires that company's own token. Capture the values from a browser session logged into the client's account. The tool stores them under the company ID the Firebase token itself authenticates as (decoded from the token), so you do NOT need to hunt down the exact internal company ID \u2014 pass whatever ID you have and it self-corrects. After this, switch_location to any of that company's locations authenticates the workflow builder correctly. DevTools capture steps: elitedcs.com/ghl-mcp-firebase.",
|
|
37704
37881
|
{
|
|
37705
|
-
companyId:
|
|
37706
|
-
name:
|
|
37707
|
-
ghl_firebase_refresh_token:
|
|
37708
|
-
ghl_user_id:
|
|
37709
|
-
ghl_firebase_api_key:
|
|
37710
|
-
test_location_id:
|
|
37882
|
+
companyId: import_zod54.z.string().describe("A GHL company/agency ID for this client (from switch_location/register_location output, or the GHL agency URL). Best-effort only: the tool re-keys the entry to the company ID the Firebase token actually authenticates as, which can differ from the ID shown in the agency URL. Just pass what you have."),
|
|
37883
|
+
name: import_zod54.z.string().describe("Friendly name for this client/company (e.g. 'Acme Health \u2014 main agency')."),
|
|
37884
|
+
ghl_firebase_refresh_token: import_zod54.z.string().min(10).describe("Firebase refresh token captured from a browser session logged into THIS company's GHL. value.stsTokenManager.refreshToken in the firebase:authUser IndexedDB row."),
|
|
37885
|
+
ghl_user_id: import_zod54.z.string().min(5).describe("Firebase User ID (uid) from the same session. value.uid in the firebase:authUser row."),
|
|
37886
|
+
ghl_firebase_api_key: import_zod54.z.string().optional().describe("Firebase API key (starts with 'AIza'). Optional \u2014 defaults to your home Firebase API key, which is identical across GHL accounts."),
|
|
37887
|
+
test_location_id: import_zod54.z.string().optional().describe("Optional but recommended: a registered location ID belonging to this company. The tool then makes a real workflow-builder call to confirm these credentials actually work for this company before you rely on them.")
|
|
37711
37888
|
},
|
|
37712
37889
|
async (args) => {
|
|
37713
37890
|
if (!registry2) {
|
|
@@ -37811,7 +37988,7 @@ Now run switch_location to any of this company's sub-accounts \u2014 the workflo
|
|
|
37811
37988
|
"unregister_company_firebase",
|
|
37812
37989
|
"Remove a company's Firebase credentials from the registry. Workflow-builder tools will stop working for that company's sub-accounts until re-registered.",
|
|
37813
37990
|
{
|
|
37814
|
-
companyId:
|
|
37991
|
+
companyId: import_zod54.z.string().describe("The company ID to remove Firebase credentials for.")
|
|
37815
37992
|
},
|
|
37816
37993
|
async ({ companyId }) => {
|
|
37817
37994
|
if (!registry2) {
|
|
@@ -37879,8 +38056,8 @@ ${lines.join("\n")}
|
|
|
37879
38056
|
"list_available_locations",
|
|
37880
38057
|
"List all GHL sub-accounts (locations) accessible with the current or agency API key. Shows locations that exist in the GHL account \u2014 use register_location to add their tokens. Offset-based pagination via skip/limit.",
|
|
37881
38058
|
{
|
|
37882
|
-
limit:
|
|
37883
|
-
skip:
|
|
38059
|
+
limit: import_zod54.z.number().optional().describe("Max locations to return. Defaults to 20."),
|
|
38060
|
+
skip: import_zod54.z.number().optional().describe("Number to skip for pagination.")
|
|
37884
38061
|
},
|
|
37885
38062
|
async ({ limit, skip }) => {
|
|
37886
38063
|
try {
|
|
@@ -37924,21 +38101,21 @@ ${lines.join("\n")}
|
|
|
37924
38101
|
"create_sub_account",
|
|
37925
38102
|
"Create a NEW GHL sub-account (location) under the agency, optionally loading a snapshot at creation \u2014 the first step of fully programmatic client provisioning (create \u2192 Blueprint build \u2192 verify_funnel). Requires an agency-level API key with the locations.write scope (add via register_agency_key). After creating, this probes whether the agency key can operate the new location and auto-registers it in the token registry when it can (switch_location then works immediately); if the probe fails, create a Private Integration Token inside the new sub-account and run register_location. Company Firebase (register_company_firebase) covers the internal-API builder tools in the new location either way.",
|
|
37926
38103
|
{
|
|
37927
|
-
name:
|
|
37928
|
-
snapshot_id:
|
|
37929
|
-
company_id:
|
|
37930
|
-
first_name:
|
|
37931
|
-
last_name:
|
|
37932
|
-
email:
|
|
37933
|
-
phone:
|
|
37934
|
-
address:
|
|
37935
|
-
city:
|
|
37936
|
-
state:
|
|
37937
|
-
postal_code:
|
|
37938
|
-
country:
|
|
37939
|
-
website:
|
|
37940
|
-
timezone:
|
|
37941
|
-
auto_register:
|
|
38104
|
+
name: import_zod54.z.string().min(1).describe("Business / sub-account name."),
|
|
38105
|
+
snapshot_id: import_zod54.z.string().optional().describe("Snapshot ID to load at creation (see list_snapshots)."),
|
|
38106
|
+
company_id: import_zod54.z.string().optional().describe("Agency company ID. Defaults to the active location's registered companyId."),
|
|
38107
|
+
first_name: import_zod54.z.string().optional().describe("Prospect/owner first name."),
|
|
38108
|
+
last_name: import_zod54.z.string().optional().describe("Prospect/owner last name."),
|
|
38109
|
+
email: import_zod54.z.string().email().optional().describe("Prospect/owner email."),
|
|
38110
|
+
phone: import_zod54.z.string().optional().describe("Business phone (E.164, e.g. +15551234567)."),
|
|
38111
|
+
address: import_zod54.z.string().optional().describe("Street address."),
|
|
38112
|
+
city: import_zod54.z.string().optional(),
|
|
38113
|
+
state: import_zod54.z.string().optional(),
|
|
38114
|
+
postal_code: import_zod54.z.string().optional(),
|
|
38115
|
+
country: import_zod54.z.string().optional().describe("Two-letter country code. Defaults to US."),
|
|
38116
|
+
website: import_zod54.z.string().optional(),
|
|
38117
|
+
timezone: import_zod54.z.string().optional().describe("IANA timezone, e.g. America/Los_Angeles."),
|
|
38118
|
+
auto_register: import_zod54.z.boolean().optional().describe("Probe the new location with the agency key and add it to the token registry on success. Default true.")
|
|
37942
38119
|
},
|
|
37943
38120
|
async (args) => {
|
|
37944
38121
|
if (readAgencyProfile().canCreateSubAccounts === false) return { content: [{ type: "text", text: "Create it in GoHighLevel and register it in GHL Command with its key." }], isError: true };
|
|
@@ -37961,9 +38138,9 @@ ${lines.join("\n")}
|
|
|
37961
38138
|
"delete_sub_account",
|
|
37962
38139
|
"Permanently delete a GHL sub-account (location) via the agency API. IRREVERSIBLE \u2014 every contact, workflow, funnel, and setting inside the sub-account is destroyed. Refuses to delete the currently active location. Also removes the location from the token registry. Requires an agency-level key with locations.write.",
|
|
37963
38140
|
{
|
|
37964
|
-
location_id:
|
|
37965
|
-
confirm:
|
|
37966
|
-
delete_twilio:
|
|
38141
|
+
location_id: import_zod54.z.string().min(1).describe("The location ID to delete."),
|
|
38142
|
+
confirm: import_zod54.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
|
|
38143
|
+
delete_twilio: import_zod54.z.boolean().optional().describe("Also delete the location's Twilio sub-account. Default false.")
|
|
37967
38144
|
},
|
|
37968
38145
|
async ({ location_id, delete_twilio }) => {
|
|
37969
38146
|
const agencyKey = registry2?.getAgencyKey();
|
|
@@ -37999,11 +38176,11 @@ ${lines.join("\n")}
|
|
|
37999
38176
|
}
|
|
38000
38177
|
);
|
|
38001
38178
|
}
|
|
38002
|
-
var
|
|
38179
|
+
var import_zod54, WRONG_LOCATION_KEY, DIFFERENT_LOCATION_KEY, switchChain;
|
|
38003
38180
|
var init_location_switcher = __esm({
|
|
38004
38181
|
"src/tools/location-switcher.ts"() {
|
|
38005
38182
|
"use strict";
|
|
38006
|
-
|
|
38183
|
+
import_zod54 = require("zod");
|
|
38007
38184
|
init_ghl_client();
|
|
38008
38185
|
init_sub_account_errors();
|
|
38009
38186
|
init_agency_profile();
|
|
@@ -38029,8 +38206,8 @@ function registerBulkOperationTools(server2, client) {
|
|
|
38029
38206
|
"bulk_add_tags",
|
|
38030
38207
|
"Add tags to multiple contacts at once. Rate-limited to avoid API throttling. Returns a summary of successes and failures.",
|
|
38031
38208
|
{
|
|
38032
|
-
contactIds:
|
|
38033
|
-
tags:
|
|
38209
|
+
contactIds: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to tag."),
|
|
38210
|
+
tags: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one tag required.").describe("Tags to add to each contact.")
|
|
38034
38211
|
},
|
|
38035
38212
|
async ({ contactIds, tags }) => {
|
|
38036
38213
|
const results = { success: 0, failed: 0, errors: [] };
|
|
@@ -38052,8 +38229,8 @@ function registerBulkOperationTools(server2, client) {
|
|
|
38052
38229
|
"bulk_remove_tags",
|
|
38053
38230
|
"Remove tags from multiple contacts at once. Rate-limited.",
|
|
38054
38231
|
{
|
|
38055
|
-
contactIds:
|
|
38056
|
-
tags:
|
|
38232
|
+
contactIds: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs."),
|
|
38233
|
+
tags: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one tag required.").describe("Tags to remove from each contact.")
|
|
38057
38234
|
},
|
|
38058
38235
|
async ({ contactIds, tags }) => {
|
|
38059
38236
|
const results = { success: 0, failed: 0, errors: [] };
|
|
@@ -38074,8 +38251,8 @@ function registerBulkOperationTools(server2, client) {
|
|
|
38074
38251
|
"bulk_update_contacts",
|
|
38075
38252
|
"Update the same field(s) on multiple contacts at once. Rate-limited. Example: set a custom field value, change source, update address for a batch of contacts.",
|
|
38076
38253
|
{
|
|
38077
|
-
contactIds:
|
|
38078
|
-
fields:
|
|
38254
|
+
contactIds: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to update."),
|
|
38255
|
+
fields: import_zod55.z.record(import_zod55.z.string(), import_zod55.z.unknown()).describe("Fields to set on each contact (e.g. {customField: {id: 'xxx', value: 'yyy'}}, {source: 'Import'}).")
|
|
38079
38256
|
},
|
|
38080
38257
|
async ({ contactIds, fields }) => {
|
|
38081
38258
|
if (Object.keys(fields).length === 0) {
|
|
@@ -38101,8 +38278,8 @@ function registerBulkOperationTools(server2, client) {
|
|
|
38101
38278
|
"bulk_add_to_workflow",
|
|
38102
38279
|
"Enroll multiple contacts into a workflow at once. Rate-limited.",
|
|
38103
38280
|
{
|
|
38104
|
-
contactIds:
|
|
38105
|
-
workflowId:
|
|
38281
|
+
contactIds: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to enroll."),
|
|
38282
|
+
workflowId: import_zod55.z.string().describe("The workflow ID to enroll contacts into.")
|
|
38106
38283
|
},
|
|
38107
38284
|
async ({ contactIds, workflowId }) => {
|
|
38108
38285
|
const results = { success: 0, failed: 0, errors: [] };
|
|
@@ -38123,8 +38300,8 @@ function registerBulkOperationTools(server2, client) {
|
|
|
38123
38300
|
"bulk_delete_contacts",
|
|
38124
38301
|
"Delete multiple contacts at once. IRREVERSIBLE. Rate-limited. Use with extreme caution.",
|
|
38125
38302
|
{
|
|
38126
|
-
contactIds:
|
|
38127
|
-
confirm:
|
|
38303
|
+
contactIds: import_zod55.z.array(import_zod55.z.string()).min(1, "At least one contact ID required.").describe("Array of contact IDs to permanently delete."),
|
|
38304
|
+
confirm: import_zod55.z.literal("DELETE").describe("Must pass the string 'DELETE' to confirm. This is a safety check.")
|
|
38128
38305
|
},
|
|
38129
38306
|
async ({ contactIds, confirm: confirm2 }) => {
|
|
38130
38307
|
if (confirm2 !== "DELETE") {
|
|
@@ -38150,11 +38327,11 @@ ${deleted.join("\n")}` : "";
|
|
|
38150
38327
|
}
|
|
38151
38328
|
);
|
|
38152
38329
|
}
|
|
38153
|
-
var
|
|
38330
|
+
var import_zod55;
|
|
38154
38331
|
var init_bulk_operations = __esm({
|
|
38155
38332
|
"src/tools/bulk-operations.ts"() {
|
|
38156
38333
|
"use strict";
|
|
38157
|
-
|
|
38334
|
+
import_zod55 = require("zod");
|
|
38158
38335
|
init_tool_helpers();
|
|
38159
38336
|
}
|
|
38160
38337
|
});
|
|
@@ -38582,8 +38759,8 @@ function registerAccountExportTools(server2, client, registry2, builderClient) {
|
|
|
38582
38759
|
"export_account",
|
|
38583
38760
|
"Export a complete inventory of the GHL sub-account: location info, contacts (count + sample), pipelines with stages, workflows (with full actions if builder auth is configured), funnels with pages, forms, custom fields, custom values, tags, calendars, and users. Returns a comprehensive JSON report for auditing or backup.",
|
|
38584
38761
|
{
|
|
38585
|
-
locationId:
|
|
38586
|
-
includeContacts:
|
|
38762
|
+
locationId: import_zod56.z.string().optional().describe("Location ID to export. Uses default if not specified."),
|
|
38763
|
+
includeContacts: import_zod56.z.boolean().optional().describe("Include contact list (first 100). Defaults to false for speed.")
|
|
38587
38764
|
},
|
|
38588
38765
|
async ({ locationId: locationId2, includeContacts }) => {
|
|
38589
38766
|
try {
|
|
@@ -38713,8 +38890,8 @@ function registerAccountExportTools(server2, client, registry2, builderClient) {
|
|
|
38713
38890
|
"compare_locations",
|
|
38714
38891
|
"Compare two GHL sub-accounts side by side. For pipelines, workflows, custom fields, tags and forms it returns the count on each side plus the NAMES present in one account and missing from the other, so a setup gap reads directly. If a section cannot be read (commonly the API key has no access to the second location), that section is reported as unavailable with the reason \u2014 never as a count of zero.",
|
|
38715
38892
|
{
|
|
38716
|
-
locationA:
|
|
38717
|
-
locationB:
|
|
38893
|
+
locationA: import_zod56.z.string().describe("First Location ID."),
|
|
38894
|
+
locationB: import_zod56.z.string().describe("Second Location ID.")
|
|
38718
38895
|
},
|
|
38719
38896
|
async ({ locationA, locationB }) => {
|
|
38720
38897
|
try {
|
|
@@ -38784,11 +38961,11 @@ function registerAccountExportTools(server2, client, registry2, builderClient) {
|
|
|
38784
38961
|
"build_offboarding_kit",
|
|
38785
38962
|
"Assemble the complete exit package for a sub-account: a full contact export, an inventory of everything the account contains, an access map of every person and connected account that can reach it, the recorded owner of each asset, a key-rotation checklist, and a plain-language handoff document the next operator can follow. Read-only: it changes nothing. locationId is REQUIRED and never defaults, because exporting the wrong client's account is the one mistake this tool must not be able to make. Assets nobody has recorded an owner for come back under ownership.unanswered \u2014 answer those with record_asset_ownership and they stay answered. Key VALUES are never included: GoHighLevel does not expose them to any tool, so the kit lists where each key lives instead.",
|
|
38786
38963
|
{
|
|
38787
|
-
locationId:
|
|
38788
|
-
contacts:
|
|
38964
|
+
locationId: import_zod56.z.string().describe("Sub-account to build the kit for. REQUIRED \u2014 this tool never falls back to the active account."),
|
|
38965
|
+
contacts: import_zod56.z.enum(["summary", "csv"]).optional().describe(
|
|
38789
38966
|
"'summary' (default) returns contact counts only. 'csv' also returns the full contact export as CSV text, which can be large \u2014 ask for it when you are ready to save the file."
|
|
38790
38967
|
),
|
|
38791
|
-
maxContacts:
|
|
38968
|
+
maxContacts: import_zod56.z.number().int().positive().optional().describe(`Ceiling on contacts read. Defaults to ${DEFAULT_MAX_CONTACTS}. If the ceiling is hit, the kit says so in the handoff document itself rather than quietly returning a short list.`)
|
|
38792
38969
|
},
|
|
38793
38970
|
async ({ locationId: locationId2, contacts, maxContacts }) => {
|
|
38794
38971
|
const originalKey = client.getApiKey();
|
|
@@ -38942,12 +39119,12 @@ function registerAccountExportTools(server2, client, registry2, builderClient) {
|
|
|
38942
39119
|
"record_asset_ownership",
|
|
38943
39120
|
"Record who owns an asset attached to a sub-account: the client, the agency, something borrowed for the engagement, or an outside vendor. Stored as custom values inside the client's own account, so the record outlives this tool and the client can read it without asking anyone. Answer these once at setup and build_offboarding_kit needs no interview later. Re-recording the same asset updates it in place instead of adding a duplicate. locationId is REQUIRED: writing ownership into the wrong client's account is worth preventing outright.",
|
|
38944
39121
|
{
|
|
38945
|
-
locationId:
|
|
38946
|
-
assets:
|
|
38947
|
-
|
|
38948
|
-
asset:
|
|
38949
|
-
owner:
|
|
38950
|
-
note:
|
|
39122
|
+
locationId: import_zod56.z.string().describe("Sub-account to record ownership in. REQUIRED \u2014 never falls back to the active account."),
|
|
39123
|
+
assets: import_zod56.z.array(
|
|
39124
|
+
import_zod56.z.object({
|
|
39125
|
+
asset: import_zod56.z.string().describe("What it is, in the words you would say out loud. e.g. 'Website domain' or 'Phone number +16025551234'."),
|
|
39126
|
+
owner: import_zod56.z.enum(OWNER_KINDS).describe("client = stays with them. agency = leaves with you. borrowed = must be returned or replaced at exit. third-party = an outside vendor controls it."),
|
|
39127
|
+
note: import_zod56.z.string().optional().describe("Anything the next person needs, e.g. 'registered on the client's own GoDaddy account'.")
|
|
38951
39128
|
})
|
|
38952
39129
|
).min(1).describe("One entry per asset. Send several at once.")
|
|
38953
39130
|
},
|
|
@@ -39066,11 +39243,11 @@ function buildLocationComparison(dataA, dataB, sections) {
|
|
|
39066
39243
|
}
|
|
39067
39244
|
return { comparison, unavailable };
|
|
39068
39245
|
}
|
|
39069
|
-
var
|
|
39246
|
+
var import_zod56, OFF_PLATFORM_ASSETS;
|
|
39070
39247
|
var init_account_export = __esm({
|
|
39071
39248
|
"src/tools/account-export.ts"() {
|
|
39072
39249
|
"use strict";
|
|
39073
|
-
|
|
39250
|
+
import_zod56 = require("zod");
|
|
39074
39251
|
init_tool_helpers();
|
|
39075
39252
|
init_contact_export();
|
|
39076
39253
|
init_asset_ownership();
|
|
@@ -39095,8 +39272,8 @@ function registerWorkflowClonerTools(server2, builderClient) {
|
|
|
39095
39272
|
"clone_workflow",
|
|
39096
39273
|
"Deep clone a workflow \u2014 creates an exact copy with new IDs for all actions, triggers, and references. The clone starts as a draft. Useful for creating templates or duplicating workflows across projects.",
|
|
39097
39274
|
{
|
|
39098
|
-
sourceWorkflowId:
|
|
39099
|
-
newName:
|
|
39275
|
+
sourceWorkflowId: import_zod57.z.string().describe("The workflow ID to clone."),
|
|
39276
|
+
newName: import_zod57.z.string().describe("Name for the cloned workflow.")
|
|
39100
39277
|
},
|
|
39101
39278
|
async ({ sourceWorkflowId, newName }) => {
|
|
39102
39279
|
try {
|
|
@@ -39224,11 +39401,11 @@ function registerWorkflowClonerTools(server2, builderClient) {
|
|
|
39224
39401
|
}
|
|
39225
39402
|
);
|
|
39226
39403
|
}
|
|
39227
|
-
var
|
|
39404
|
+
var import_zod57, crypto6;
|
|
39228
39405
|
var init_workflow_cloner = __esm({
|
|
39229
39406
|
"src/tools/workflow-cloner.ts"() {
|
|
39230
39407
|
"use strict";
|
|
39231
|
-
|
|
39408
|
+
import_zod57 = require("zod");
|
|
39232
39409
|
crypto6 = __toESM(require("crypto"));
|
|
39233
39410
|
}
|
|
39234
39411
|
});
|
|
@@ -39270,11 +39447,11 @@ ${text2}`);
|
|
|
39270
39447
|
"list_smart_lists",
|
|
39271
39448
|
"List smart lists (dynamic / saved-filter lists) in a location. Smart Lists are saved searches over contacts or opportunities \u2014 agencies use them to segment by complex criteria. Filters and columns aren't returned in the list view; use get_smart_list for the full filter spec.",
|
|
39272
39449
|
{
|
|
39273
|
-
objectKey:
|
|
39274
|
-
query:
|
|
39275
|
-
limit:
|
|
39276
|
-
startAfter:
|
|
39277
|
-
locationId:
|
|
39450
|
+
objectKey: import_zod58.z.enum(OBJECT_KEYS).describe("The object type the lists segment over. 'contacts' for contact-segments, 'opportunity' for opportunity-segments. Required \u2014 GHL rejects requests without it."),
|
|
39451
|
+
query: import_zod58.z.string().optional().describe("Free-text search across smart list names."),
|
|
39452
|
+
limit: import_zod58.z.number().optional().describe("Max smart lists per page. Defaults to 20 on GHL's side."),
|
|
39453
|
+
startAfter: import_zod58.z.string().optional().describe("Cursor for pagination \u2014 pass the last list's id from the previous page."),
|
|
39454
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39278
39455
|
},
|
|
39279
39456
|
async ({ objectKey, query, limit, startAfter, locationId: locationId2 }) => {
|
|
39280
39457
|
try {
|
|
@@ -39307,10 +39484,10 @@ ${await res.text()}`);
|
|
|
39307
39484
|
"create_contact_smart_list",
|
|
39308
39485
|
"Create a CONTACT smart list (saved filter over contacts) with working filters \u2014 the owner-exception views a dashboard needs. Uses GHL's contacts/smartlist service and its captured OR-of-AND filter envelope. `tagFilters` is the common case: each entry is a list of tag names the contact must ALL carry. For anything else pass `rawFilters` (leaf objects: {field, operator, value, options}). NOTE: create_smart_list (the older tool) targets the opportunity-list service and cannot express contact filters.",
|
|
39309
39486
|
{
|
|
39310
|
-
name:
|
|
39311
|
-
tagFilters:
|
|
39312
|
-
rawFilters:
|
|
39313
|
-
locationId:
|
|
39487
|
+
name: import_zod58.z.string().describe("Display name for the smart list."),
|
|
39488
|
+
tagFilters: import_zod58.z.array(import_zod58.z.array(import_zod58.z.string())).optional().describe('Tag conditions. Each inner array is one filter requiring ALL those tags, e.g. [["escalated"]] for "has the escalated tag".'),
|
|
39489
|
+
rawFilters: import_zod58.z.array(import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown())).optional().describe("Advanced: raw filter leaves {field, operator, value, options}. Combined with tagFilters into one AND group."),
|
|
39490
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39314
39491
|
},
|
|
39315
39492
|
async ({ name, tagFilters, rawFilters, locationId: locationId2 }) => {
|
|
39316
39493
|
try {
|
|
@@ -39334,7 +39511,7 @@ ${await res.text()}`);
|
|
|
39334
39511
|
"get_contact_smart_list",
|
|
39335
39512
|
"Get a CONTACT smart list by id INCLUDING its filterSpecs. Use this to verify a contact smart list's filters after creating it \u2014 get_smart_list targets the opportunity-list service and returns the record without any filters, which reads as 'no filters set'.",
|
|
39336
39513
|
{
|
|
39337
|
-
listId:
|
|
39514
|
+
listId: import_zod58.z.string().describe("The contact smart list id.")
|
|
39338
39515
|
},
|
|
39339
39516
|
async ({ listId }) => {
|
|
39340
39517
|
try {
|
|
@@ -39352,8 +39529,8 @@ ${await res.text()}`);
|
|
|
39352
39529
|
"delete_contact_smart_list",
|
|
39353
39530
|
"Delete a CONTACT smart list by id.",
|
|
39354
39531
|
{
|
|
39355
|
-
listId:
|
|
39356
|
-
confirm:
|
|
39532
|
+
listId: import_zod58.z.string().describe("The contact smart list id."),
|
|
39533
|
+
confirm: import_zod58.z.literal("DELETE").describe('Type "DELETE" to confirm.')
|
|
39357
39534
|
},
|
|
39358
39535
|
async ({ listId }) => {
|
|
39359
39536
|
try {
|
|
@@ -39371,8 +39548,8 @@ ${await res.text()}`);
|
|
|
39371
39548
|
"get_smart_list",
|
|
39372
39549
|
"Get a single smart list by ID with its full configuration: filters, columns, permissions, and metadata. The filters array is what defines who/what is in the list.",
|
|
39373
39550
|
{
|
|
39374
|
-
listId:
|
|
39375
|
-
locationId:
|
|
39551
|
+
listId: import_zod58.z.string().describe("The smart list ID (from list_smart_lists or a previous create_smart_list response)."),
|
|
39552
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39376
39553
|
},
|
|
39377
39554
|
async ({ listId, locationId: locationId2 }) => {
|
|
39378
39555
|
try {
|
|
@@ -39388,13 +39565,13 @@ ${await res.text()}`);
|
|
|
39388
39565
|
"create_smart_list",
|
|
39389
39566
|
"Create a new smart list (dynamic filter list). Required: name + objectKey. Filters define the saved-search criteria \u2014 pass an empty array to create an empty list and add filters later via update_smart_list. The shape of filters/columns is opaque here; query an existing smart list with get_smart_list to see the format GHL expects.",
|
|
39390
39567
|
{
|
|
39391
|
-
name:
|
|
39392
|
-
objectKey:
|
|
39393
|
-
filters:
|
|
39394
|
-
columns:
|
|
39395
|
-
pipelineIds:
|
|
39396
|
-
defaultInPipelines:
|
|
39397
|
-
locationId:
|
|
39568
|
+
name: import_zod58.z.string().describe("Display name for the smart list."),
|
|
39569
|
+
objectKey: import_zod58.z.enum(OBJECT_KEYS).describe("Object type the list segments over. 'contacts' or 'opportunity'."),
|
|
39570
|
+
filters: import_zod58.z.array(import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown())).optional().describe("Array of filter objects. Each object has fields like {field, operator, value} \u2014 exact shape varies by filter type. See get_smart_list on an existing list to learn the format."),
|
|
39571
|
+
columns: import_zod58.z.array(import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown())).optional().describe("Array of column definitions for the smart list view in GHL UI. Each defines which contact/opportunity field shows as a column. Defaults to GHL's standard columns if omitted."),
|
|
39572
|
+
pipelineIds: import_zod58.z.array(import_zod58.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs to restrict this smart list to. Empty array = all pipelines."),
|
|
39573
|
+
defaultInPipelines: import_zod58.z.array(import_zod58.z.string()).optional().describe("(opportunity objectKey only) Pipeline IDs where this list is the default view."),
|
|
39574
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39398
39575
|
},
|
|
39399
39576
|
async ({ name, objectKey, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
|
|
39400
39577
|
try {
|
|
@@ -39415,13 +39592,13 @@ ${await res.text()}`);
|
|
|
39415
39592
|
"update_smart_list",
|
|
39416
39593
|
"Update an existing smart list's name, filters, or columns. The objectKey CANNOT be changed after creation (GHL rejects with 422 if you try). Use get_smart_list first to inspect the current filters; partial updates work \u2014 pass only the fields you want to change.",
|
|
39417
39594
|
{
|
|
39418
|
-
listId:
|
|
39419
|
-
name:
|
|
39420
|
-
filters:
|
|
39421
|
-
columns:
|
|
39422
|
-
pipelineIds:
|
|
39423
|
-
defaultInPipelines:
|
|
39424
|
-
locationId:
|
|
39595
|
+
listId: import_zod58.z.string().describe("The smart list ID to update."),
|
|
39596
|
+
name: import_zod58.z.string().optional().describe("New display name."),
|
|
39597
|
+
filters: import_zod58.z.array(import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown())).optional().describe("Replace the filter array entirely. To add a filter, fetch the current list, append, and pass the new array."),
|
|
39598
|
+
columns: import_zod58.z.array(import_zod58.z.record(import_zod58.z.string(), import_zod58.z.unknown())).optional().describe("Replace the column array entirely."),
|
|
39599
|
+
pipelineIds: import_zod58.z.array(import_zod58.z.string()).optional().describe("(opportunity only) Update the pipeline scope."),
|
|
39600
|
+
defaultInPipelines: import_zod58.z.array(import_zod58.z.string()).optional().describe("(opportunity only) Update the default-in-pipelines list."),
|
|
39601
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39425
39602
|
},
|
|
39426
39603
|
async ({ listId, name, filters, columns, pipelineIds, defaultInPipelines, locationId: locationId2 }) => {
|
|
39427
39604
|
try {
|
|
@@ -39446,9 +39623,9 @@ ${await res.text()}`);
|
|
|
39446
39623
|
"delete_smart_list",
|
|
39447
39624
|
"Permanently delete a smart list. IRREVERSIBLE. The list configuration is removed but the contacts/opportunities themselves are NOT touched \u2014 smart lists are just saved filters. Any workflow trigger / dashboard / report that referenced this list by ID will stop working.",
|
|
39448
39625
|
{
|
|
39449
|
-
listId:
|
|
39450
|
-
confirm:
|
|
39451
|
-
locationId:
|
|
39626
|
+
listId: import_zod58.z.string().describe("The smart list ID to delete."),
|
|
39627
|
+
confirm: import_zod58.z.literal("DELETE").describe("Must pass 'DELETE' to confirm this destructive action."),
|
|
39628
|
+
locationId: import_zod58.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39452
39629
|
},
|
|
39453
39630
|
async ({ listId, locationId: locationId2 }) => {
|
|
39454
39631
|
try {
|
|
@@ -39461,11 +39638,11 @@ ${await res.text()}`);
|
|
|
39461
39638
|
}
|
|
39462
39639
|
);
|
|
39463
39640
|
}
|
|
39464
|
-
var
|
|
39641
|
+
var import_zod58, SMARTLIST_BASE, OBJECT_KEYS, CONTACT_SMARTLIST_BASE, CONTACT_SMARTLIST_DELETE, DEFAULT_CONTACT_SMARTLIST_COLUMNS;
|
|
39465
39642
|
var init_smart_lists = __esm({
|
|
39466
39643
|
"src/tools/smart-lists.ts"() {
|
|
39467
39644
|
"use strict";
|
|
39468
|
-
|
|
39645
|
+
import_zod58 = require("zod");
|
|
39469
39646
|
init_workflow_builder_client();
|
|
39470
39647
|
init_tool_helpers();
|
|
39471
39648
|
SMARTLIST_BASE = "https://backend.leadconnectorhq.com/lists/dynamic";
|
|
@@ -39504,7 +39681,7 @@ ${text2}`);
|
|
|
39504
39681
|
"get_review_link_list",
|
|
39505
39682
|
"List the review-link destinations configured for a location \u2014 the platforms (Google, Facebook, etc.) where review requests send contacts. Each entry has a label and the public review URL. Useful for: building review-request workflows (the workflow goal condition `review_request_clicked` references these review-link ids), and auditing which review platforms a sub-account has connected.",
|
|
39506
39683
|
{
|
|
39507
|
-
locationId:
|
|
39684
|
+
locationId: import_zod59.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39508
39685
|
},
|
|
39509
39686
|
async ({ locationId: locationId2 }) => {
|
|
39510
39687
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39516,13 +39693,13 @@ ${text2}`);
|
|
|
39516
39693
|
"list_reviews",
|
|
39517
39694
|
"List the reviews a location has received (Google, Facebook, etc.) with rating, author, text, reply status, sentiment score, and source. Filter by an exact star rating or a range \u2014 'everything under 4 stars' is minRating 1 + maxRating 3. NOTE: location is resolved through nested filter params internally \u2014 a flat locationId is what caused the long-standing 'No Location Found' error, now fixed. Responding to a review is not yet available via API. Requires Firebase auth.",
|
|
39518
39695
|
{
|
|
39519
|
-
locationId:
|
|
39520
|
-
pageNumber:
|
|
39521
|
-
pageSize:
|
|
39522
|
-
rating:
|
|
39523
|
-
minRating:
|
|
39524
|
-
maxRating:
|
|
39525
|
-
includeDeleted:
|
|
39696
|
+
locationId: import_zod59.z.string().optional().describe("Location ID. Falls back to the active builder client's location."),
|
|
39697
|
+
pageNumber: import_zod59.z.number().optional().describe("1-based page number. Defaults to 1."),
|
|
39698
|
+
pageSize: import_zod59.z.number().optional().describe("Results per page. Defaults to 10."),
|
|
39699
|
+
rating: import_zod59.z.number().min(1).max(5).optional().describe("Exact star rating (1-5). Takes precedence over minRating/maxRating."),
|
|
39700
|
+
minRating: import_zod59.z.number().min(1).max(5).optional().describe("Lowest star rating to include (1-5), inclusive."),
|
|
39701
|
+
maxRating: import_zod59.z.number().min(1).max(5).optional().describe("Highest star rating to include (1-5), inclusive. For 'under 4 stars', pass 3."),
|
|
39702
|
+
includeDeleted: import_zod59.z.boolean().optional().describe("Include deleted reviews. Defaults to false.")
|
|
39526
39703
|
},
|
|
39527
39704
|
async ({ locationId: locationId2, pageNumber, pageSize, rating, minRating, maxRating, includeDeleted }) => {
|
|
39528
39705
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39560,11 +39737,11 @@ function buildReviewsQuery(locationId2, opts = {}) {
|
|
|
39560
39737
|
}).join("&");
|
|
39561
39738
|
return `/reviews?${query}`;
|
|
39562
39739
|
}
|
|
39563
|
-
var
|
|
39740
|
+
var import_zod59, REPUTATION_BASE;
|
|
39564
39741
|
var init_reputation = __esm({
|
|
39565
39742
|
"src/tools/reputation.ts"() {
|
|
39566
39743
|
"use strict";
|
|
39567
|
-
|
|
39744
|
+
import_zod59 = require("zod");
|
|
39568
39745
|
init_workflow_builder_client();
|
|
39569
39746
|
init_tool_helpers();
|
|
39570
39747
|
REPUTATION_BASE = "https://backend.leadconnectorhq.com/reputation";
|
|
@@ -39580,15 +39757,15 @@ function registerEmailCampaignTools(server2, builderClient) {
|
|
|
39580
39757
|
"create_email_campaign",
|
|
39581
39758
|
"Create an email campaign / broadcast DRAFT from an existing email template. Requires a templateId (create one first with create_email_template). The campaign is created as a draft \u2014 to actually SEND or schedule it, finish in the GHL UI: the send/schedule endpoint isn't available through the API yet. There's also no API delete for campaigns, so drafts created here are removed via the GHL UI. Despite those limits, this gets the campaign 90% built \u2014 template, subject, sender, name all set programmatically.",
|
|
39582
39759
|
{
|
|
39583
|
-
templateId:
|
|
39584
|
-
name:
|
|
39585
|
-
subject:
|
|
39586
|
-
fromName:
|
|
39587
|
-
fromEmail:
|
|
39588
|
-
isPlainText:
|
|
39589
|
-
enableResendToUnopened:
|
|
39590
|
-
hasUtmTracking:
|
|
39591
|
-
locationId:
|
|
39760
|
+
templateId: import_zod60.z.string().describe("ID of an email template (from create_email_template or list_email_templates) to use as the campaign body."),
|
|
39761
|
+
name: import_zod60.z.string().optional().describe("Internal campaign name (shown in the campaigns list, not to recipients). Defaults to a GHL-generated name."),
|
|
39762
|
+
subject: import_zod60.z.string().optional().describe("Email subject line recipients see."),
|
|
39763
|
+
fromName: import_zod60.z.string().optional().describe("Sender display name."),
|
|
39764
|
+
fromEmail: import_zod60.z.string().optional().describe("Sender email address. Must be a verified sending address in the location."),
|
|
39765
|
+
isPlainText: import_zod60.z.boolean().optional().describe("Send as plain text instead of HTML. Defaults to false."),
|
|
39766
|
+
enableResendToUnopened: import_zod60.z.boolean().optional().describe("Auto-resend to contacts who didn't open. Defaults to false."),
|
|
39767
|
+
hasUtmTracking: import_zod60.z.boolean().optional().describe("Append UTM tracking params to links. Defaults to false."),
|
|
39768
|
+
locationId: import_zod60.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39592
39769
|
},
|
|
39593
39770
|
async ({ templateId, name, subject, fromName, fromEmail, isPlainText, enableResendToUnopened, hasUtmTracking, locationId: locationId2 }) => {
|
|
39594
39771
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39615,11 +39792,11 @@ ${text2}`);
|
|
|
39615
39792
|
}
|
|
39616
39793
|
);
|
|
39617
39794
|
}
|
|
39618
|
-
var
|
|
39795
|
+
var import_zod60, SVC_BASE;
|
|
39619
39796
|
var init_email_campaigns = __esm({
|
|
39620
39797
|
"src/tools/email-campaigns.ts"() {
|
|
39621
39798
|
"use strict";
|
|
39622
|
-
|
|
39799
|
+
import_zod60 = require("zod");
|
|
39623
39800
|
init_workflow_builder_client();
|
|
39624
39801
|
init_tool_helpers();
|
|
39625
39802
|
SVC_BASE = "https://services.leadconnectorhq.com";
|
|
@@ -39646,7 +39823,7 @@ ${text2}`);
|
|
|
39646
39823
|
"list_membership_offers",
|
|
39647
39824
|
"List a location's membership offers and products in one call. Returns { products: [...], offers: [...] }. Products are courses/communities; offers are the access grants (what a contact gets enrolled in). Use the returned ids with membership trigger conditions like offer_access_granted / product_completed.",
|
|
39648
39825
|
{
|
|
39649
|
-
locationId:
|
|
39826
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39650
39827
|
},
|
|
39651
39828
|
async ({ locationId: locationId2 }) => {
|
|
39652
39829
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39658,8 +39835,8 @@ ${text2}`);
|
|
|
39658
39835
|
"list_membership_categories",
|
|
39659
39836
|
"List all membership/course categories in a location. Categories group lessons inside a course/product. Use the returned ids with the category_completed / category_started trigger conditions. READ-ONLY.",
|
|
39660
39837
|
{
|
|
39661
|
-
limit:
|
|
39662
|
-
locationId:
|
|
39838
|
+
limit: import_zod61.z.number().optional().describe("Max categories to return. Defaults to a large value (effectively all)."),
|
|
39839
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39663
39840
|
},
|
|
39664
39841
|
async ({ limit, locationId: locationId2 }) => {
|
|
39665
39842
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39671,8 +39848,8 @@ ${text2}`);
|
|
|
39671
39848
|
"list_membership_lessons",
|
|
39672
39849
|
"List all membership/course lessons in a location. Use the returned ids with the lesson_completed / lesson_started trigger conditions. READ-ONLY.",
|
|
39673
39850
|
{
|
|
39674
|
-
limit:
|
|
39675
|
-
locationId:
|
|
39851
|
+
limit: import_zod61.z.number().optional().describe("Max lessons to return. Defaults to a large value (effectively all)."),
|
|
39852
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39676
39853
|
},
|
|
39677
39854
|
async ({ limit, locationId: locationId2 }) => {
|
|
39678
39855
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39684,9 +39861,9 @@ ${text2}`);
|
|
|
39684
39861
|
"create_course",
|
|
39685
39862
|
"Create a membership course (a 'product') in a location. Creates the course shell with a title and description; add categories (create_membership_category) and lessons (create_membership_lesson) into it, and an offer (create_membership_offer) to grant access. Returns the new product, including its id. Requires Firebase auth. NOTE: GoHighLevel's membership API exposes no delete or update for this, so it cannot be removed or renamed through the product afterwards \u2014 only in the GHL UI. Create deliberately.",
|
|
39686
39863
|
{
|
|
39687
|
-
title:
|
|
39688
|
-
description:
|
|
39689
|
-
locationId:
|
|
39864
|
+
title: import_zod61.z.string().describe("Course title."),
|
|
39865
|
+
description: import_zod61.z.string().optional().describe("Course description. Defaults to empty."),
|
|
39866
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39690
39867
|
},
|
|
39691
39868
|
async ({ title: title2, description, locationId: locationId2 }) => {
|
|
39692
39869
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39698,13 +39875,13 @@ ${text2}`);
|
|
|
39698
39875
|
"create_membership_category",
|
|
39699
39876
|
"Create a category (module/section) inside a membership course. Categories group lessons. Needs the productId of the course (from create_course or list_membership_offers). Returns the new category, including its id (use it as categoryId when creating lessons). Requires Firebase auth. NOTE: GoHighLevel's membership API exposes no delete or update for this, so it cannot be removed or renamed through the product afterwards \u2014 only in the GHL UI. Create deliberately.",
|
|
39700
39877
|
{
|
|
39701
|
-
title:
|
|
39702
|
-
productId:
|
|
39703
|
-
description:
|
|
39704
|
-
visibility:
|
|
39705
|
-
sequenceNo:
|
|
39706
|
-
dripDays:
|
|
39707
|
-
locationId:
|
|
39878
|
+
title: import_zod61.z.string().describe("Category title (e.g. 'Module 1')."),
|
|
39879
|
+
productId: import_zod61.z.string().describe("The course/product id this category belongs to."),
|
|
39880
|
+
description: import_zod61.z.string().optional().describe("Category description. Defaults to empty."),
|
|
39881
|
+
visibility: import_zod61.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
|
|
39882
|
+
sequenceNo: import_zod61.z.number().optional().describe("Display order within the course. Defaults to 0."),
|
|
39883
|
+
dripDays: import_zod61.z.number().optional().describe("Days after enrollment before this category unlocks. Defaults to 0 (no drip)."),
|
|
39884
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39708
39885
|
},
|
|
39709
39886
|
async ({ title: title2, productId, description, visibility, sequenceNo, dripDays, locationId: locationId2 }) => {
|
|
39710
39887
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39716,14 +39893,14 @@ ${text2}`);
|
|
|
39716
39893
|
"create_membership_lesson",
|
|
39717
39894
|
"Create a lesson (a 'post') inside a membership course category. Needs both the categoryId (from create_membership_category) and the productId of the course. Description is the lesson body as HTML. Returns the new lesson, including its id. Requires Firebase auth. NOTE: GoHighLevel's membership API exposes no delete or update for this, so it cannot be removed or renamed through the product afterwards \u2014 only in the GHL UI. Create deliberately.",
|
|
39718
39895
|
{
|
|
39719
|
-
title:
|
|
39720
|
-
categoryId:
|
|
39721
|
-
productId:
|
|
39722
|
-
description:
|
|
39723
|
-
contentType:
|
|
39724
|
-
visibility:
|
|
39725
|
-
sequenceNo:
|
|
39726
|
-
locationId:
|
|
39896
|
+
title: import_zod61.z.string().describe("Lesson title."),
|
|
39897
|
+
categoryId: import_zod61.z.string().describe("The category id this lesson belongs to (from create_membership_category)."),
|
|
39898
|
+
productId: import_zod61.z.string().describe("The course/product id this lesson belongs to."),
|
|
39899
|
+
description: import_zod61.z.string().optional().describe("Lesson body as HTML. Defaults to empty."),
|
|
39900
|
+
contentType: import_zod61.z.enum(["video", "audio", "text", "pdf", "assignment"]).optional().describe("Lesson content type. Defaults to 'video'."),
|
|
39901
|
+
visibility: import_zod61.z.enum(["published", "draft"]).optional().describe("'published' (default) or 'draft'."),
|
|
39902
|
+
sequenceNo: import_zod61.z.number().optional().describe("Display order within the category. Defaults to 0."),
|
|
39903
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39727
39904
|
},
|
|
39728
39905
|
async ({ title: title2, categoryId, productId, description, contentType, visibility, sequenceNo, locationId: locationId2 }) => {
|
|
39729
39906
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39735,12 +39912,12 @@ ${text2}`);
|
|
|
39735
39912
|
"create_membership_offer",
|
|
39736
39913
|
"Create a membership offer \u2014 the access grant that enrolls contacts into one or more courses/products. Link it to course product ids. Defaults to a free offer; for paid, set type to 'recurring' or 'one_time' with an amount. Returns the new offer, including its id (referenced by the offer_access_granted trigger). Requires Firebase auth. NOTE: GoHighLevel's membership API exposes no delete or update for this, so it cannot be removed or renamed through the product afterwards \u2014 only in the GHL UI. Create deliberately.",
|
|
39737
39914
|
{
|
|
39738
|
-
title:
|
|
39739
|
-
productIds:
|
|
39740
|
-
type:
|
|
39741
|
-
amount:
|
|
39742
|
-
currency:
|
|
39743
|
-
locationId:
|
|
39915
|
+
title: import_zod61.z.string().describe("Offer title (shown at checkout / in the offer list)."),
|
|
39916
|
+
productIds: import_zod61.z.array(import_zod61.z.string()).describe("Course/product ids this offer grants access to (from create_course or list_membership_offers)."),
|
|
39917
|
+
type: import_zod61.z.enum(["free", "recurring", "one_time"]).optional().describe("Offer type. Defaults to 'free'."),
|
|
39918
|
+
amount: import_zod61.z.number().optional().describe("Price for paid offers. Defaults to 0 (free)."),
|
|
39919
|
+
currency: import_zod61.z.string().optional().describe("Currency code for paid offers. Defaults to 'USD'."),
|
|
39920
|
+
locationId: import_zod61.z.string().optional().describe("Location ID. Falls back to the active builder client's location.")
|
|
39744
39921
|
},
|
|
39745
39922
|
async ({ title: title2, productIds, type, amount, currency, locationId: locationId2 }) => {
|
|
39746
39923
|
const loc = locationId2 ?? client.locationId;
|
|
@@ -39807,11 +39984,11 @@ function buildOfferPayload(o) {
|
|
|
39807
39984
|
currency: o.currency ?? "USD"
|
|
39808
39985
|
};
|
|
39809
39986
|
}
|
|
39810
|
-
var
|
|
39987
|
+
var import_zod61, MEMBERSHIP_BASE2;
|
|
39811
39988
|
var init_memberships = __esm({
|
|
39812
39989
|
"src/tools/memberships.ts"() {
|
|
39813
39990
|
"use strict";
|
|
39814
|
-
|
|
39991
|
+
import_zod61 = require("zod");
|
|
39815
39992
|
init_workflow_builder_client();
|
|
39816
39993
|
init_tool_helpers();
|
|
39817
39994
|
MEMBERSHIP_BASE2 = "https://backend.leadconnectorhq.com/membership";
|
|
@@ -39907,7 +40084,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
39907
40084
|
"get_template_questionnaire",
|
|
39908
40085
|
"Get the questionnaire for a specific template. Returns all the questions that need to be answered before deploying. Present these to the user one at a time in a conversational style.",
|
|
39909
40086
|
{
|
|
39910
|
-
templateFile:
|
|
40087
|
+
templateFile: import_zod62.z.string().describe("Path to the template JSON file (from list_templates).")
|
|
39911
40088
|
},
|
|
39912
40089
|
async ({ templateFile }) => {
|
|
39913
40090
|
try {
|
|
@@ -39940,11 +40117,11 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
39940
40117
|
"deploy_template",
|
|
39941
40118
|
"Deploy a template to set up a GHL sub-account. Creates tags, custom fields, pipelines with stages, calendars, workflows, and forms based on the template and the user's questionnaire answers. This is the main setup automation tool.",
|
|
39942
40119
|
{
|
|
39943
|
-
templateFile:
|
|
39944
|
-
answers:
|
|
39945
|
-
locationId:
|
|
39946
|
-
dryRun:
|
|
39947
|
-
overwriteBusinessInfo:
|
|
40120
|
+
templateFile: import_zod62.z.string().describe("Path to the template JSON file."),
|
|
40121
|
+
answers: import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown()).describe("Questionnaire answers keyed by question ID (e.g. {business_name: 'My Clinic', business_phone: '+15551234567', ...})."),
|
|
40122
|
+
locationId: import_zod62.z.string().optional().describe("Location ID to deploy to. Uses default if not specified."),
|
|
40123
|
+
dryRun: import_zod62.z.boolean().optional().describe("If true, shows what would be created without actually creating anything. Defaults to false."),
|
|
40124
|
+
overwriteBusinessInfo: import_zod62.z.boolean().optional().describe(
|
|
39948
40125
|
"Allow this deploy to CHANGE business details the sub-account already has (legal name, address, city, state, postal code, country, phone, email, website). Off by default: those are the fields A2P / 10DLC brand registration is filed against, so changing them on a live account can put SMS registration at risk. Blank fields are always filled without needing this. Only pass true when you intend to correct the account's business record and know its A2P state."
|
|
39949
40126
|
)
|
|
39950
40127
|
},
|
|
@@ -40324,45 +40501,45 @@ ${errors.join("\n")}` : dryRun ? "\nNo errors!" : "\nNo errors \u2014 every item
|
|
|
40324
40501
|
}
|
|
40325
40502
|
);
|
|
40326
40503
|
}
|
|
40327
|
-
var
|
|
40504
|
+
var import_zod62, fs28, path27, TemplateSchema;
|
|
40328
40505
|
var init_template_deployer = __esm({
|
|
40329
40506
|
"src/tools/template-deployer.ts"() {
|
|
40330
40507
|
"use strict";
|
|
40331
|
-
|
|
40508
|
+
import_zod62 = require("zod");
|
|
40332
40509
|
init_workflow_builder_client();
|
|
40333
40510
|
init_tool_helpers();
|
|
40334
40511
|
fs28 = __toESM(require("fs"));
|
|
40335
40512
|
path27 = __toESM(require("path"));
|
|
40336
40513
|
init_workflow_builder_client();
|
|
40337
|
-
TemplateSchema =
|
|
40338
|
-
templateName:
|
|
40339
|
-
templateVersion:
|
|
40340
|
-
description:
|
|
40341
|
-
questionnaire:
|
|
40342
|
-
id:
|
|
40343
|
-
question:
|
|
40344
|
-
type:
|
|
40345
|
-
required:
|
|
40346
|
-
placeholder:
|
|
40514
|
+
TemplateSchema = import_zod62.z.object({
|
|
40515
|
+
templateName: import_zod62.z.string(),
|
|
40516
|
+
templateVersion: import_zod62.z.string().optional(),
|
|
40517
|
+
description: import_zod62.z.string().optional().default(""),
|
|
40518
|
+
questionnaire: import_zod62.z.array(import_zod62.z.object({
|
|
40519
|
+
id: import_zod62.z.string(),
|
|
40520
|
+
question: import_zod62.z.string(),
|
|
40521
|
+
type: import_zod62.z.string(),
|
|
40522
|
+
required: import_zod62.z.boolean().optional(),
|
|
40523
|
+
placeholder: import_zod62.z.string().optional()
|
|
40347
40524
|
})).optional().default([]),
|
|
40348
|
-
location:
|
|
40349
|
-
tags:
|
|
40350
|
-
customFields:
|
|
40351
|
-
name:
|
|
40352
|
-
dataType:
|
|
40525
|
+
location: import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown()).optional(),
|
|
40526
|
+
tags: import_zod62.z.array(import_zod62.z.string()).optional(),
|
|
40527
|
+
customFields: import_zod62.z.array(import_zod62.z.object({
|
|
40528
|
+
name: import_zod62.z.string(),
|
|
40529
|
+
dataType: import_zod62.z.string()
|
|
40353
40530
|
})).optional(),
|
|
40354
|
-
pipelines:
|
|
40355
|
-
name:
|
|
40356
|
-
stages:
|
|
40531
|
+
pipelines: import_zod62.z.array(import_zod62.z.object({
|
|
40532
|
+
name: import_zod62.z.string(),
|
|
40533
|
+
stages: import_zod62.z.array(import_zod62.z.object({ position: import_zod62.z.number(), name: import_zod62.z.string() }))
|
|
40357
40534
|
})).optional(),
|
|
40358
|
-
workflows:
|
|
40359
|
-
name:
|
|
40360
|
-
condition:
|
|
40361
|
-
actions:
|
|
40535
|
+
workflows: import_zod62.z.array(import_zod62.z.object({
|
|
40536
|
+
name: import_zod62.z.string(),
|
|
40537
|
+
condition: import_zod62.z.string().optional(),
|
|
40538
|
+
actions: import_zod62.z.array(import_zod62.z.record(import_zod62.z.string(), import_zod62.z.unknown())).optional().default([])
|
|
40362
40539
|
})).optional(),
|
|
40363
|
-
calendars:
|
|
40364
|
-
name:
|
|
40365
|
-
description:
|
|
40540
|
+
calendars: import_zod62.z.array(import_zod62.z.object({
|
|
40541
|
+
name: import_zod62.z.string(),
|
|
40542
|
+
description: import_zod62.z.string().optional()
|
|
40366
40543
|
})).optional()
|
|
40367
40544
|
});
|
|
40368
40545
|
}
|
|
@@ -40842,8 +41019,8 @@ function registerCheckupTools(server2, installedVersion, client, builderClient,
|
|
|
40842
41019
|
"run_checkup",
|
|
40843
41020
|
`The full GHL Command checkup \u2014 the answer to "am I using it all, and is it all still working?". Three sections: (1) CONNECTION, the same install checks as health_check; (2) WHAT STILL WORKS, one live read-only probe against every GHL capability family, which distinguishes "working but you own none" from "GoHighLevel changed something and this is broken"; (3) WHAT YOU ARE NOT USING YET, derived from what the account actually contains, each line carrying the exact sentence to paste back. Read-only and safe on live client accounts. Use format:'json' for a machine-readable report. Run it monthly, after a GHL update, or any time something feels off.`,
|
|
40844
41021
|
{
|
|
40845
|
-
locationId:
|
|
40846
|
-
format:
|
|
41022
|
+
locationId: import_zod63.z.string().optional().describe("Sub-account to check. Defaults to the active location."),
|
|
41023
|
+
format: import_zod63.z.enum(["report", "json"]).optional().describe("'report' (default) is the plain-English checkup. 'json' returns the same data for scripts and support tickets.")
|
|
40847
41024
|
},
|
|
40848
41025
|
async ({ locationId: locationId2, format }) => {
|
|
40849
41026
|
let loc;
|
|
@@ -40878,11 +41055,11 @@ function registerCheckupTools(server2, installedVersion, client, builderClient,
|
|
|
40878
41055
|
}
|
|
40879
41056
|
);
|
|
40880
41057
|
}
|
|
40881
|
-
var
|
|
41058
|
+
var import_zod63, PROBES, BUILDER_PROBE_KEY, CONFIRM_ATTEMPTS, CONFIRM_DELAY_MS, BUILDER_AUTH_FAILURE, VERDICT_SYMBOL;
|
|
40882
41059
|
var init_checkup = __esm({
|
|
40883
41060
|
"src/tools/checkup.ts"() {
|
|
40884
41061
|
"use strict";
|
|
40885
|
-
|
|
41062
|
+
import_zod63 = require("zod");
|
|
40886
41063
|
init_diagnostics();
|
|
40887
41064
|
PROBES = [
|
|
40888
41065
|
{ key: "contacts", label: "Contacts", path: "/contacts/", params: { locationId: "{loc}", limit: 1 }, listKeys: ["contacts"] },
|
|
@@ -40966,7 +41143,7 @@ function registerUserGuideTools(server2) {
|
|
|
40966
41143
|
server2.tool(
|
|
40967
41144
|
"get_user_guide",
|
|
40968
41145
|
"List what is in the GHL Command user guide \u2014 the library of plain-English guides with copy-paste prompts (count and report contacts, build a nurture sequence, audit workflows, and more). Ships with every install, free or paid. By default this only RETURNS the contents list and the guide's file path; it changes nothing on screen. Set open=true ONLY when the person has actually asked to see the guide, because opening it takes over their screen with a browser window in front of whatever they are doing.",
|
|
40969
|
-
{ open:
|
|
41146
|
+
{ 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.") },
|
|
40970
41147
|
async ({ open }) => {
|
|
40971
41148
|
const guidePath = resolveGuidePath();
|
|
40972
41149
|
if (!fs29.existsSync(guidePath)) {
|
|
@@ -41002,12 +41179,12 @@ ${guidePath}`;
|
|
|
41002
41179
|
}
|
|
41003
41180
|
);
|
|
41004
41181
|
}
|
|
41005
|
-
var import_child_process6,
|
|
41182
|
+
var import_child_process6, import_zod64, fs29, path28;
|
|
41006
41183
|
var init_user_guide = __esm({
|
|
41007
41184
|
"src/tools/user-guide.ts"() {
|
|
41008
41185
|
"use strict";
|
|
41009
41186
|
import_child_process6 = require("child_process");
|
|
41010
|
-
|
|
41187
|
+
import_zod64 = require("zod");
|
|
41011
41188
|
fs29 = __toESM(require("fs"));
|
|
41012
41189
|
path28 = __toESM(require("path"));
|
|
41013
41190
|
}
|
|
@@ -41045,30 +41222,30 @@ function registerAgencyProfileTools(server2) {
|
|
|
41045
41222
|
"set_agency_profile",
|
|
41046
41223
|
"Save or update YOUR agency's profile (see get_agency_profile). Send only the fields you are changing \u2014 anything you omit is left exactly as it was, so this is safe to call repeatedly to fill the profile in a few pieces. To CLEAR a field, pass null for it. The brand colour must be #rrggbb. Local file on this machine; never touches GoHighLevel.",
|
|
41047
41224
|
{
|
|
41048
|
-
agencyName:
|
|
41049
|
-
legalName:
|
|
41050
|
-
accent:
|
|
41051
|
-
logoUrl:
|
|
41052
|
-
website:
|
|
41053
|
-
sender:
|
|
41054
|
-
fromName:
|
|
41055
|
-
replyTo:
|
|
41056
|
-
phone:
|
|
41225
|
+
agencyName: import_zod65.z.string().optional().describe("How the agency is named to clients."),
|
|
41226
|
+
legalName: import_zod65.z.string().optional().describe("Registered entity, when it differs from the trading name (contracts, A2P)."),
|
|
41227
|
+
accent: import_zod65.z.string().optional().describe("Brand colour as #rrggbb. Anything else is rejected rather than stored."),
|
|
41228
|
+
logoUrl: import_zod65.z.string().optional(),
|
|
41229
|
+
website: import_zod65.z.string().optional(),
|
|
41230
|
+
sender: import_zod65.z.object({
|
|
41231
|
+
fromName: import_zod65.z.string().optional(),
|
|
41232
|
+
replyTo: import_zod65.z.string().optional(),
|
|
41233
|
+
phone: import_zod65.z.string().optional()
|
|
41057
41234
|
}).optional().describe("Who a client-facing message comes from."),
|
|
41058
|
-
offers:
|
|
41059
|
-
pricing:
|
|
41060
|
-
minMonthly:
|
|
41061
|
-
minSetup:
|
|
41062
|
-
currency:
|
|
41235
|
+
offers: import_zod65.z.array(OfferSchema).optional(),
|
|
41236
|
+
pricing: import_zod65.z.object({
|
|
41237
|
+
minMonthly: import_zod65.z.number().min(0).optional(),
|
|
41238
|
+
minSetup: import_zod65.z.number().min(0).optional(),
|
|
41239
|
+
currency: import_zod65.z.string().optional()
|
|
41063
41240
|
}).optional(),
|
|
41064
|
-
delivery:
|
|
41065
|
-
sops:
|
|
41066
|
-
hoursToDeliverTarget:
|
|
41241
|
+
delivery: import_zod65.z.object({
|
|
41242
|
+
sops: import_zod65.z.array(import_zod65.z.string()).optional(),
|
|
41243
|
+
hoursToDeliverTarget: import_zod65.z.number().min(0).optional()
|
|
41067
41244
|
}).optional(),
|
|
41068
|
-
staff:
|
|
41069
|
-
defaults:
|
|
41070
|
-
timezone:
|
|
41071
|
-
industry:
|
|
41245
|
+
staff: import_zod65.z.array(StaffSchema).optional(),
|
|
41246
|
+
defaults: import_zod65.z.object({
|
|
41247
|
+
timezone: import_zod65.z.string().optional(),
|
|
41248
|
+
industry: import_zod65.z.string().optional()
|
|
41072
41249
|
}).optional()
|
|
41073
41250
|
},
|
|
41074
41251
|
async (args) => {
|
|
@@ -41079,24 +41256,24 @@ function registerAgencyProfileTools(server2) {
|
|
|
41079
41256
|
}
|
|
41080
41257
|
);
|
|
41081
41258
|
}
|
|
41082
|
-
var
|
|
41259
|
+
var import_zod65, OfferSchema, StaffSchema;
|
|
41083
41260
|
var init_agency_profile2 = __esm({
|
|
41084
41261
|
"src/tools/agency-profile.ts"() {
|
|
41085
41262
|
"use strict";
|
|
41086
|
-
|
|
41263
|
+
import_zod65 = require("zod");
|
|
41087
41264
|
init_tool_helpers();
|
|
41088
41265
|
init_agency_profile();
|
|
41089
|
-
OfferSchema =
|
|
41090
|
-
name:
|
|
41091
|
-
priceFloor:
|
|
41092
|
-
cadence:
|
|
41093
|
-
notes:
|
|
41266
|
+
OfferSchema = import_zod65.z.object({
|
|
41267
|
+
name: import_zod65.z.string().describe("What you sell it as, in the words you use on a call."),
|
|
41268
|
+
priceFloor: import_zod65.z.number().min(0).optional().describe("The number below which you don't take the work."),
|
|
41269
|
+
cadence: import_zod65.z.enum(["one-time", "monthly"]).optional(),
|
|
41270
|
+
notes: import_zod65.z.string().optional()
|
|
41094
41271
|
});
|
|
41095
|
-
StaffSchema =
|
|
41096
|
-
name:
|
|
41097
|
-
role:
|
|
41098
|
-
email:
|
|
41099
|
-
mayApprove:
|
|
41272
|
+
StaffSchema = import_zod65.z.object({
|
|
41273
|
+
name: import_zod65.z.string(),
|
|
41274
|
+
role: import_zod65.z.string().optional(),
|
|
41275
|
+
email: import_zod65.z.string().optional(),
|
|
41276
|
+
mayApprove: import_zod65.z.array(import_zod65.z.string()).optional().describe("Advisory list of what this person signs off on. The approval gates are enforced by the ritual, not by this field.")
|
|
41100
41277
|
});
|
|
41101
41278
|
}
|
|
41102
41279
|
});
|
|
@@ -42585,7 +42762,7 @@ function checkConversion(p, ctx, rendered) {
|
|
|
42585
42762
|
{
|
|
42586
42763
|
const ctaColours = primaries.map((c) => ({ c, ch: chroma(c.bg) })).filter((x) => x.ch !== null);
|
|
42587
42764
|
const all = [...ctaColours.map((x) => ({ hex: hexOf(x.c.bg), ch: x.ch })), ...p.sections.map((s) => ({ hex: hexOf(s.bg), ch: chroma(s.bg) })).filter((x) => x.hex && x.ch !== null)];
|
|
42588
|
-
const hottest = all.sort((a,
|
|
42765
|
+
const hottest = all.sort((a, z84) => z84.ch - a.ch)[0] ?? null;
|
|
42589
42766
|
const bad = [];
|
|
42590
42767
|
if (primaries.length === 0) bad.push("no primary CTA on the page for the hot colour to be on");
|
|
42591
42768
|
if (!hottest) bad.push("no readable background colour on any CTA or section");
|
|
@@ -42612,7 +42789,7 @@ function checkConversion(p, ctx, rendered) {
|
|
|
42612
42789
|
if (real.length > 1) bad.push(`primary CTAs split across ${real.length} destinations (${real.join(", ")})`);
|
|
42613
42790
|
if (real.length === 0) bad.push("no primary CTA resolves to a form, calendar, tel: or checkout");
|
|
42614
42791
|
if (p.docHeight > 5e3 && primaries.length < 5) bad.push(`${primaries.length} primary CTAs on a ${Math.round(p.docHeight)} px page (needs 5+)`);
|
|
42615
|
-
const ys = primaries.map((c) => c.y).sort((a,
|
|
42792
|
+
const ys = primaries.map((c) => c.y).sort((a, z84) => a - z84);
|
|
42616
42793
|
let maxGap = ys.length ? ys[0] : p.docHeight;
|
|
42617
42794
|
for (let i = 1; i < ys.length; i++) maxGap = Math.max(maxGap, ys[i] - ys[i - 1]);
|
|
42618
42795
|
if (ys.length) maxGap = Math.max(maxGap, p.docHeight - ys[ys.length - 1]);
|
|
@@ -43604,23 +43781,23 @@ function normaliseNull(v) {
|
|
|
43604
43781
|
for (const k of ["firstBreakdown", "fix"]) if (o[k] === "null" || o[k] === "") o[k] = null;
|
|
43605
43782
|
return o;
|
|
43606
43783
|
}
|
|
43607
|
-
var
|
|
43784
|
+
var import_zod66, COPY_STANDARD, Score, CopyGradeSchema;
|
|
43608
43785
|
var init_copy_grade = __esm({
|
|
43609
43786
|
"src/command-os/design/copy-grade.ts"() {
|
|
43610
43787
|
"use strict";
|
|
43611
|
-
|
|
43788
|
+
import_zod66 = require("zod");
|
|
43612
43789
|
init_copy();
|
|
43613
43790
|
COPY_STANDARD = copy_default;
|
|
43614
|
-
Score =
|
|
43615
|
-
CopyGradeSchema =
|
|
43616
|
-
verdict:
|
|
43617
|
-
icp:
|
|
43618
|
-
icpInferred:
|
|
43619
|
-
scores:
|
|
43620
|
-
firstBreakdown:
|
|
43621
|
-
evidence:
|
|
43622
|
-
fix:
|
|
43623
|
-
summary:
|
|
43791
|
+
Score = import_zod66.z.number().int().min(1).max(5);
|
|
43792
|
+
CopyGradeSchema = import_zod66.z.object({
|
|
43793
|
+
verdict: import_zod66.z.enum(["PASS", "FAIL"]),
|
|
43794
|
+
icp: import_zod66.z.string(),
|
|
43795
|
+
icpInferred: import_zod66.z.boolean().optional(),
|
|
43796
|
+
scores: import_zod66.z.object({ attention: Score, resonance: Score, belief: Score, action: Score }),
|
|
43797
|
+
firstBreakdown: import_zod66.z.enum(["attention", "resonance", "belief", "action"]).nullable(),
|
|
43798
|
+
evidence: import_zod66.z.record(import_zod66.z.string(), import_zod66.z.string()).optional(),
|
|
43799
|
+
fix: import_zod66.z.string().nullable(),
|
|
43800
|
+
summary: import_zod66.z.string()
|
|
43624
43801
|
});
|
|
43625
43802
|
}
|
|
43626
43803
|
});
|
|
@@ -43713,7 +43890,9 @@ async function defaultVerify(file2, medium, brand, outDir, mode, headless = true
|
|
|
43713
43890
|
}
|
|
43714
43891
|
async function runDesign(opts) {
|
|
43715
43892
|
const dna = resolveBrand(opts.brand);
|
|
43716
|
-
const
|
|
43893
|
+
const checkedBrief = DesignBriefSchema.safeParse({ schemaVersion: 1, brandSlug: dna.slug, ...opts.brief });
|
|
43894
|
+
if (!checkedBrief.success) throw new Error(designValidationMessage(checkedBrief.error));
|
|
43895
|
+
const brief = checkedBrief.data;
|
|
43717
43896
|
const medium = brief.medium;
|
|
43718
43897
|
const outDir = (0, import_node_path7.resolve)(opts.outDir);
|
|
43719
43898
|
(0, import_node_fs7.mkdirSync)(outDir, { recursive: true });
|
|
@@ -43807,6 +43986,7 @@ var init_run = __esm({
|
|
|
43807
43986
|
init_verify();
|
|
43808
43987
|
init_looks();
|
|
43809
43988
|
init_store();
|
|
43989
|
+
init_brief_validation();
|
|
43810
43990
|
init_schema();
|
|
43811
43991
|
DEFAULT_MODEL = "opus";
|
|
43812
43992
|
FIRST = `You have no tools in this session. Write the complete file now and reply with ONLY the HTML, in one \`\`\`html fenced block, nothing before or after it. Do not describe it; do not ask; output the file.`;
|
|
@@ -43820,13 +44000,13 @@ function registerDesignStudioTools(server2) {
|
|
|
43820
44000
|
"extract_brand",
|
|
43821
44001
|
"Read a website's brand and store it on this machine: six colours, two faces, radius, logo, headline voice, each with its source and a confidence. Asks nothing of any account. Static read first; add useBrowser:true for a rendered pass in the automation browser (needed for script-built sites); a Firecrawl key, if you have one, is used as a fourth source. State your rights to the site once; it is recorded, not checked.",
|
|
43822
44002
|
{
|
|
43823
|
-
url:
|
|
43824
|
-
rights:
|
|
43825
|
-
name:
|
|
43826
|
-
slug:
|
|
43827
|
-
useBrowser:
|
|
43828
|
-
firecrawlKey:
|
|
43829
|
-
overwrite:
|
|
44003
|
+
url: import_zod67.z.string().url().describe("The site to read, e.g. https://example-clinic.com"),
|
|
44004
|
+
rights: import_zod67.z.enum(RIGHTS).describe("own \xB7 client-authorized \xB7 written-permission \xB7 none. Recorded on the brand; with 'none' the logo is still stored for reference but flagged."),
|
|
44005
|
+
name: import_zod67.z.string().optional().describe("Brand name; defaults to the page title."),
|
|
44006
|
+
slug: import_zod67.z.string().optional().describe("Folder name for the brand; defaults from the name."),
|
|
44007
|
+
useBrowser: import_zod67.z.boolean().optional().describe("Also render the page in the automation browser for computed colours, the real display face and a screenshot. Automatic when the static read looks like a script-built shell."),
|
|
44008
|
+
firecrawlKey: import_zod67.z.string().optional().describe("Optional. Uses Firecrawl's branding format as an extra source. Never stored."),
|
|
44009
|
+
overwrite: import_zod67.z.boolean().optional().describe("Replace an existing brand with this slug. Default false: refuses rather than clobbers.")
|
|
43830
44010
|
},
|
|
43831
44011
|
async (a) => {
|
|
43832
44012
|
const raw = await extractStatic(a.url);
|
|
@@ -43859,9 +44039,9 @@ function registerDesignStudioTools(server2) {
|
|
|
43859
44039
|
"import_reference_look",
|
|
43860
44040
|
`Fetch one reference look (Apple, Notion, Stripe, Linear and about seventy more) from ${REFERENCE_SOURCE.repo} at a pinned commit onto this machine, as a starting palette: colours, faces, radius, tone. Never the reference brand's logo, never its mark-usage rules, and the pages you make must not name it. Then pull the client's own logo and accent over it with set_brand.`,
|
|
43861
44041
|
{
|
|
43862
|
-
look:
|
|
43863
|
-
as:
|
|
43864
|
-
overwrite:
|
|
44042
|
+
look: import_zod67.z.string().describe("The reference slug, e.g. apple, notion, stripe, linear.app. list_brands shows them all."),
|
|
44043
|
+
as: import_zod67.z.string().optional().describe("Store under this slug instead (e.g. 'northside-clinic-start'). Default: 'look-<look>'."),
|
|
44044
|
+
overwrite: import_zod67.z.boolean().optional()
|
|
43865
44045
|
},
|
|
43866
44046
|
async (a) => {
|
|
43867
44047
|
const entry = listReferenceLooks().find((e) => e.slug === a.look);
|
|
@@ -43891,7 +44071,7 @@ function registerDesignStudioTools(server2) {
|
|
|
43891
44071
|
server2,
|
|
43892
44072
|
"get_brand",
|
|
43893
44073
|
"Read one brand in full: colours with OKLCH twins and contrast-corrected text shades, faces and what renders, shape, logo, voice, and the provenance of every value. House looks (calm-clinic, bold-local, quiet-luxury, night-studio, warm-practice, civic-clear) read without being stored first.",
|
|
43894
|
-
{ slug:
|
|
44074
|
+
{ slug: import_zod67.z.string().describe("A stored brand's slug or a house look's slug.") },
|
|
43895
44075
|
async ({ slug: slug3 }) => {
|
|
43896
44076
|
const house = houseLooks().find((d) => d.slug === slug3);
|
|
43897
44077
|
const dna = house ?? readBrand(slugify(slug3));
|
|
@@ -43903,22 +44083,22 @@ function registerDesignStudioTools(server2) {
|
|
|
43903
44083
|
"set_brand",
|
|
43904
44084
|
"Change values on a stored brand, or copy a house look into a stored brand. Send only what changes; every change is recorded in the brand's provenance with who and when. Colours are #rrggbb; faces are family names (a licensed face is kept and its Google fallback recorded).",
|
|
43905
44085
|
{
|
|
43906
|
-
slug:
|
|
43907
|
-
as:
|
|
43908
|
-
name:
|
|
43909
|
-
accent:
|
|
43910
|
-
bg:
|
|
43911
|
-
fg:
|
|
43912
|
-
muted:
|
|
43913
|
-
border:
|
|
43914
|
-
surface:
|
|
43915
|
-
displayFont:
|
|
43916
|
-
bodyFont:
|
|
43917
|
-
radiusMd:
|
|
43918
|
-
logoSvg:
|
|
43919
|
-
tone:
|
|
43920
|
-
audience:
|
|
43921
|
-
changedBy:
|
|
44086
|
+
slug: import_zod67.z.string().describe("The brand to change, or a house-look slug to copy."),
|
|
44087
|
+
as: import_zod67.z.string().optional().describe("When copying a house look: the new slug to store it under."),
|
|
44088
|
+
name: import_zod67.z.string().optional(),
|
|
44089
|
+
accent: import_zod67.z.string().optional(),
|
|
44090
|
+
bg: import_zod67.z.string().optional(),
|
|
44091
|
+
fg: import_zod67.z.string().optional(),
|
|
44092
|
+
muted: import_zod67.z.string().optional(),
|
|
44093
|
+
border: import_zod67.z.string().optional(),
|
|
44094
|
+
surface: import_zod67.z.string().optional(),
|
|
44095
|
+
displayFont: import_zod67.z.string().optional(),
|
|
44096
|
+
bodyFont: import_zod67.z.string().optional(),
|
|
44097
|
+
radiusMd: import_zod67.z.number().min(0).max(64).optional(),
|
|
44098
|
+
logoSvg: import_zod67.z.string().optional().describe("Inline SVG for the client's own logo; replaces the stored one."),
|
|
44099
|
+
tone: import_zod67.z.string().optional(),
|
|
44100
|
+
audience: import_zod67.z.string().optional(),
|
|
44101
|
+
changedBy: import_zod67.z.string().optional().describe("Who is making the change; goes into provenance.")
|
|
43922
44102
|
},
|
|
43923
44103
|
async (a) => {
|
|
43924
44104
|
const house = houseLooks().find((d2) => d2.slug === a.slug);
|
|
@@ -43985,19 +44165,19 @@ function registerDesignStudioTools(server2) {
|
|
|
43985
44165
|
"compose_design_brief",
|
|
43986
44166
|
"The pack your own Claude writes from: brand tokens ready to paste, the rules it will be graded on with thresholds, the output contract (one self-contained file, the data markers the verifier reads), the sameness tells as banned, and the file name. Calls no model. Write the file, then lint_design_contract, then verify_design.",
|
|
43987
44167
|
{
|
|
43988
|
-
medium:
|
|
43989
|
-
brandSlug:
|
|
43990
|
-
headline:
|
|
43991
|
-
points:
|
|
43992
|
-
audience:
|
|
43993
|
-
job:
|
|
43994
|
-
ctaLabel:
|
|
43995
|
-
proof:
|
|
43996
|
-
mode:
|
|
43997
|
-
ratio:
|
|
43998
|
-
theme:
|
|
43999
|
-
logo:
|
|
44000
|
-
slides:
|
|
44168
|
+
medium: import_zod67.z.enum(MEDIA).describe("deck (16:9 slides) \xB7 site (one responsive page) \xB7 report (a branded document)"),
|
|
44169
|
+
brandSlug: import_zod67.z.string().describe("A stored brand or a house look."),
|
|
44170
|
+
headline: import_zod67.z.string().describe("The one-line message."),
|
|
44171
|
+
points: import_zod67.z.array(import_zod67.z.string()).max(12).describe("Deck: 3\u20135 points. Site: the sections in order. Report: the section titles."),
|
|
44172
|
+
audience: import_zod67.z.string().optional(),
|
|
44173
|
+
job: import_zod67.z.string().optional().describe("Site: the ONE thing a visitor should do."),
|
|
44174
|
+
ctaLabel: import_zod67.z.string().optional(),
|
|
44175
|
+
proof: import_zod67.z.string().optional().describe("One line of proof for the hero."),
|
|
44176
|
+
mode: import_zod67.z.enum(["presenter", "document"]).optional().describe("Deck only. Default presenter."),
|
|
44177
|
+
ratio: import_zod67.z.number().min(1.1).max(1.7).optional().describe("Type scale ratio. Default 1.333."),
|
|
44178
|
+
theme: import_zod67.z.enum(["light", "dark", "both"]).optional().describe("Default both."),
|
|
44179
|
+
logo: import_zod67.z.enum(["bottom-left", "bottom-right", "top-left", "omit"]).optional().describe("Deck logo placement. Default bottom-left."),
|
|
44180
|
+
slides: import_zod67.z.number().int().min(3).max(24).optional()
|
|
44001
44181
|
},
|
|
44002
44182
|
async (a) => {
|
|
44003
44183
|
const dna = houseLooks().find((d) => d.slug === a.brandSlug) ?? readBrand(slugify(a.brandSlug));
|
|
@@ -44016,7 +44196,7 @@ function registerDesignStudioTools(server2) {
|
|
|
44016
44196
|
server2,
|
|
44017
44197
|
"lint_design_contract",
|
|
44018
44198
|
"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.",
|
|
44019
|
-
{ file:
|
|
44199
|
+
{ file: import_zod67.z.string().describe("Path to the .html file."), medium: import_zod67.z.enum(MEDIA) },
|
|
44020
44200
|
async ({ file: file2, medium }) => {
|
|
44021
44201
|
const abs = path30.resolve(file2);
|
|
44022
44202
|
if (!fs31.existsSync(abs)) throw new Error(`No file at ${abs}.`);
|
|
@@ -44029,14 +44209,14 @@ function registerDesignStudioTools(server2) {
|
|
|
44029
44209
|
"verify_design",
|
|
44030
44210
|
"Render a deck or page (a local .html or a published URL) in the automation browser and measure it against the design rules and the sameness tells. Every rule on its own line: measured, proxy or judgment call, with the value and the evidence. Writes verification.json, verification.html and screenshots to outDir. PASS means zero failed measured rules and NOTHING MORE: every rule measures craft \u2014 type, spacing, colour, contrast, structure, performance and the machine-made tells \u2014 and none of them measures whether the page persuades anyone. A page can pass every rule and still be boring and unsellable. Never report a pass as a quality claim; judge the writing separately. GoHighLevel pages: pass placement:'ghl' to grade on the builder subset.",
|
|
44031
44211
|
{
|
|
44032
|
-
target:
|
|
44033
|
-
medium:
|
|
44034
|
-
brandSlug:
|
|
44035
|
-
outDir:
|
|
44036
|
-
placement:
|
|
44037
|
-
mode:
|
|
44038
|
-
ratio:
|
|
44039
|
-
logo:
|
|
44212
|
+
target: import_zod67.z.string().describe("A local .html path or an http(s) URL."),
|
|
44213
|
+
medium: import_zod67.z.enum(MEDIA),
|
|
44214
|
+
brandSlug: import_zod67.z.string().optional().describe("The brand to measure against (accent share, logo, faces)."),
|
|
44215
|
+
outDir: import_zod67.z.string().optional().describe("Where to write the report. Default: <file dir>/verify-<name>/ or ./verify-<host>/ for URLs."),
|
|
44216
|
+
placement: import_zod67.z.enum(["html", "ghl"]).optional(),
|
|
44217
|
+
mode: import_zod67.z.enum(["presenter", "document"]).optional(),
|
|
44218
|
+
ratio: import_zod67.z.number().optional(),
|
|
44219
|
+
logo: import_zod67.z.enum(["keep", "omit"]).optional()
|
|
44040
44220
|
},
|
|
44041
44221
|
async (a) => {
|
|
44042
44222
|
const brand = a.brandSlug ? houseLooks().find((d) => d.slug === a.brandSlug) ?? readBrand(slugify(a.brandSlug)) : void 0;
|
|
@@ -44067,18 +44247,18 @@ function registerDesignStudioTools(server2) {
|
|
|
44067
44247
|
"check_page_theme",
|
|
44068
44248
|
"After Page Studio has saved a GoHighLevel page with a brand_to_theme theme, read the page back and prove the brand landed: each of the six slots is measured in the data GoHighLevel actually saved, and the donor template's own colour or face must be GONE \u2014 a half-finished swap is the failure this catches, and it is invisible in a preview snapshot. Also confirms the page's preview address by fetching it, so verify_design has a real URL to grade. Reads only: no key, no account API, nothing written.",
|
|
44069
44249
|
{
|
|
44070
|
-
pageDataDownloadUrl:
|
|
44071
|
-
file:
|
|
44072
|
-
brandSlug:
|
|
44073
|
-
theme:
|
|
44074
|
-
primary:
|
|
44075
|
-
primarySoft:
|
|
44076
|
-
surface:
|
|
44077
|
-
textDark:
|
|
44078
|
-
headingFont:
|
|
44079
|
-
bodyFont:
|
|
44250
|
+
pageDataDownloadUrl: import_zod67.z.string().url().optional().describe("The pageDataDownloadUrl from get_page_full. The page data is fetched here so the whole envelope never has to pass through the conversation."),
|
|
44251
|
+
file: import_zod67.z.string().optional().describe("Alternative to the URL: a local .json file holding the saved page data."),
|
|
44252
|
+
brandSlug: import_zod67.z.string().optional().describe("The stored brand whose theme was sent. Defaults to comparing against the theme passed in `theme`."),
|
|
44253
|
+
theme: import_zod67.z.object({
|
|
44254
|
+
primary: import_zod67.z.string().optional(),
|
|
44255
|
+
primarySoft: import_zod67.z.string().optional(),
|
|
44256
|
+
surface: import_zod67.z.string().optional(),
|
|
44257
|
+
textDark: import_zod67.z.string().optional(),
|
|
44258
|
+
headingFont: import_zod67.z.string().optional(),
|
|
44259
|
+
bodyFont: import_zod67.z.string().optional()
|
|
44080
44260
|
}).optional().describe("The theme that was sent, if it was not brand_to_theme's output for brandSlug."),
|
|
44081
|
-
pageId:
|
|
44261
|
+
pageId: import_zod67.z.string().optional().describe("The page id. With it, the preview address is derived and FETCHED once, and reported only if it answered.")
|
|
44082
44262
|
},
|
|
44083
44263
|
async (a) => {
|
|
44084
44264
|
if (!a.pageDataDownloadUrl && !a.file) throw new Error("Pass pageDataDownloadUrl (from get_page_full) or file.");
|
|
@@ -44118,10 +44298,10 @@ function registerDesignStudioTools(server2) {
|
|
|
44118
44298
|
"grade_copy",
|
|
44119
44299
|
"Grade whether a page or deck would actually persuade the reader it is for \u2014 the half the design rules cannot reach. Scores the four sequential jobs persuasive copy must do, in reading order (Attention, Resonance, Belief, Action), names the FIRST place the right prospect falls off, and gives a concrete rewrite of that one thing. This is a JUDGEMENT made by a model on YOUR OWN Claude subscription, never our key, and it is deliberately separate from verify_design: a page is ready when both pass, and a design pass on its own says nothing about whether anyone would buy.",
|
|
44120
44300
|
{
|
|
44121
|
-
file:
|
|
44122
|
-
text:
|
|
44123
|
-
icp:
|
|
44124
|
-
model:
|
|
44301
|
+
file: import_zod67.z.string().optional().describe("Path to the .html file to grade."),
|
|
44302
|
+
text: import_zod67.z.string().optional().describe("Alternative to file: the copy itself."),
|
|
44303
|
+
icp: import_zod67.z.string().optional().describe("Who the page is for, in one line, e.g. 'agency owners drowning in client onboarding'. Without it the grader infers a reader and says so \u2014 and grading against the wrong reader is the commonest way this comes back wrong."),
|
|
44304
|
+
model: import_zod67.z.string().optional().describe("Which engine grades it. Defaults to the runner's default.")
|
|
44125
44305
|
},
|
|
44126
44306
|
async (a) => {
|
|
44127
44307
|
if (!a.file && !a.text) throw new Error("Pass file or text.");
|
|
@@ -44156,7 +44336,7 @@ function registerDesignStudioTools(server2) {
|
|
|
44156
44336
|
server2,
|
|
44157
44337
|
"brand_to_theme",
|
|
44158
44338
|
"The six-slot theme Page Studio's compose_page / compose_website accept, derived from a stored brand, with the warnings about what the donor cannot carry. A re-skin of the donor template, not a port of the design system. Pass the theme straight through as compose_page's `theme`.",
|
|
44159
|
-
{ slug:
|
|
44339
|
+
{ slug: import_zod67.z.string() },
|
|
44160
44340
|
async ({ slug: slug3 }) => {
|
|
44161
44341
|
const d = houseLooks().find((x) => x.slug === slug3) ?? readBrand(slugify(slug3));
|
|
44162
44342
|
const { warnings, ...theme } = d.pageStudioTheme;
|
|
@@ -44164,11 +44344,11 @@ function registerDesignStudioTools(server2) {
|
|
|
44164
44344
|
}
|
|
44165
44345
|
);
|
|
44166
44346
|
}
|
|
44167
|
-
var
|
|
44347
|
+
var import_zod67, fs31, path30, summarise2;
|
|
44168
44348
|
var init_design_studio = __esm({
|
|
44169
44349
|
"src/tools/design-studio.ts"() {
|
|
44170
44350
|
"use strict";
|
|
44171
|
-
|
|
44351
|
+
import_zod67 = require("zod");
|
|
44172
44352
|
fs31 = __toESM(require("node:fs"));
|
|
44173
44353
|
path30 = __toESM(require("node:path"));
|
|
44174
44354
|
init_tool_helpers();
|
|
@@ -44992,9 +45172,9 @@ function registerConnectorTools(server2) {
|
|
|
44992
45172
|
"connect_system",
|
|
44993
45173
|
"Store a connector's credentials on THIS machine so that system can be read. The key is written next to your GoHighLevel credentials, readable only by you, and is never sent anywhere, never returned by any tool, and never written to a log. Today: Stripe, which answers who owes you money, what each person or company pays you and when they renew.",
|
|
44994
45174
|
{
|
|
44995
|
-
system:
|
|
44996
|
-
key:
|
|
44997
|
-
label:
|
|
45175
|
+
system: import_zod68.z.enum(["stripe"]).describe("The system to connect. Stripe is the one that is built."),
|
|
45176
|
+
key: import_zod68.z.string().min(8).describe("A Stripe RESTRICTED key with read access to subscriptions, invoices and customers. A restricted key is deliberate: nothing here writes, so nothing here needs a key that can."),
|
|
45177
|
+
label: import_zod68.z.string().optional().describe("Which account this is, so you can tell two apart later.")
|
|
44998
45178
|
},
|
|
44999
45179
|
async ({ system, key, label }) => {
|
|
45000
45180
|
const h = await health(stripeFetcher(key));
|
|
@@ -45048,7 +45228,7 @@ function registerConnectorTools(server2) {
|
|
|
45048
45228
|
server2,
|
|
45049
45229
|
"disconnect_system",
|
|
45050
45230
|
"Remove a connector's credentials from this machine. The data already written into your income records stays; only the ability to read that system again is removed.",
|
|
45051
|
-
{ system:
|
|
45231
|
+
{ system: import_zod68.z.enum(["stripe"]) },
|
|
45052
45232
|
async ({ system }) => ({
|
|
45053
45233
|
removed: disconnect(system),
|
|
45054
45234
|
note: "Anything already written into your income records is untouched."
|
|
@@ -45059,10 +45239,10 @@ function registerConnectorTools(server2) {
|
|
|
45059
45239
|
"sync_stripe",
|
|
45060
45240
|
"Read your Stripe and fill in what your records do not know: what each person or company pays you, when they renew, and who owes you money. Handles both shapes of income \u2014 clients whose GoHighLevel accounts you run (confirmLinks) and people who simply pay you, like software subscribers and event fees (recordAsIncome). DRY RUN BY DEFAULT \u2014 it shows you every change before making any. A client whose Stripe customer cannot be matched to one of your clients is reported, never guessed at.",
|
|
45061
45241
|
{
|
|
45062
|
-
apply:
|
|
45063
|
-
confirmLinks:
|
|
45064
|
-
stripeCustomerId:
|
|
45065
|
-
clientKey:
|
|
45242
|
+
apply: import_zod68.z.boolean().optional().describe("Write the changes into your income records. Defaults to false: look first."),
|
|
45243
|
+
confirmLinks: import_zod68.z.array(import_zod68.z.object({
|
|
45244
|
+
stripeCustomerId: import_zod68.z.string(),
|
|
45245
|
+
clientKey: import_zod68.z.string().describe("The client in your records this Stripe customer is.")
|
|
45066
45246
|
})).optional().describe("Matches you are confirming \u2014 a Stripe customer that IS one of the clients already in your records. An email match alone is never enough to move money against a client's name."),
|
|
45067
45247
|
// ADDED 2026-09-01, and it is the half that was missing. confirmLinks can
|
|
45068
45248
|
// only point a Stripe customer at a record that already exists. On Jerry's
|
|
@@ -45070,10 +45250,10 @@ function registerConnectorTools(server2) {
|
|
|
45070
45250
|
// buy software and the record only held GoHighLevel accounts. There was no
|
|
45071
45251
|
// move that turned "Stripe knows Dane pays $97" into a record — so the
|
|
45072
45252
|
// brief kept saying "no client has a commercial record yet" forever.
|
|
45073
|
-
recordAsIncome:
|
|
45074
|
-
stripeCustomerId:
|
|
45075
|
-
kind:
|
|
45076
|
-
clientName:
|
|
45253
|
+
recordAsIncome: import_zod68.z.array(import_zod68.z.object({
|
|
45254
|
+
stripeCustomerId: import_zod68.z.string(),
|
|
45255
|
+
kind: import_zod68.z.enum(["software", "other"]).describe("'software' for a licence or subscription, 'other' for an event fee, revenue share or training. Use confirmLinks instead if this customer is a client whose GoHighLevel account you run."),
|
|
45256
|
+
clientName: import_zod68.z.string().optional().describe("What to call them. Defaults to the name on the Stripe customer.")
|
|
45077
45257
|
})).optional().describe("Stripe customers that are income in their own right, with no GoHighLevel account. Creates a record keyed by the Stripe customer id and fills in what they pay and when they renew, from Stripe.")
|
|
45078
45258
|
},
|
|
45079
45259
|
async ({ apply, confirmLinks, recordAsIncome }) => {
|
|
@@ -45233,11 +45413,11 @@ function registerConnectorTools(server2) {
|
|
|
45233
45413
|
}
|
|
45234
45414
|
);
|
|
45235
45415
|
}
|
|
45236
|
-
var
|
|
45416
|
+
var import_zod68;
|
|
45237
45417
|
var init_connectors = __esm({
|
|
45238
45418
|
"src/tools/connectors.ts"() {
|
|
45239
45419
|
"use strict";
|
|
45240
|
-
|
|
45420
|
+
import_zod68 = require("zod");
|
|
45241
45421
|
init_tool_helpers();
|
|
45242
45422
|
init_store3();
|
|
45243
45423
|
init_stripe();
|
|
@@ -45297,22 +45477,22 @@ function removeCalendarSecret() {
|
|
|
45297
45477
|
throw new Error("The Google Calendar sign-in could not be removed from this machine.");
|
|
45298
45478
|
}
|
|
45299
45479
|
}
|
|
45300
|
-
var fs33, path32, import_node_crypto9,
|
|
45480
|
+
var fs33, path32, import_node_crypto9, import_zod69, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
|
|
45301
45481
|
var init_google_calendar_store = __esm({
|
|
45302
45482
|
"src/command-os/connectors/google-calendar-store.ts"() {
|
|
45303
45483
|
"use strict";
|
|
45304
45484
|
fs33 = __toESM(require("node:fs"));
|
|
45305
45485
|
path32 = __toESM(require("node:path"));
|
|
45306
45486
|
import_node_crypto9 = require("node:crypto");
|
|
45307
|
-
|
|
45487
|
+
import_zod69 = require("zod");
|
|
45308
45488
|
init_credentials_store();
|
|
45309
45489
|
CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
45310
|
-
SecretSchema =
|
|
45311
|
-
clientId:
|
|
45312
|
-
clientSecret:
|
|
45313
|
-
refreshToken:
|
|
45314
|
-
scope:
|
|
45315
|
-
connectedAt:
|
|
45490
|
+
SecretSchema = import_zod69.z.object({
|
|
45491
|
+
clientId: import_zod69.z.string().min(1),
|
|
45492
|
+
clientSecret: import_zod69.z.string().min(1),
|
|
45493
|
+
refreshToken: import_zod69.z.string().min(8),
|
|
45494
|
+
scope: import_zod69.z.literal(CALENDAR_SCOPE),
|
|
45495
|
+
connectedAt: import_zod69.z.string()
|
|
45316
45496
|
});
|
|
45317
45497
|
googleCalendarPath = () => path32.join(appDataDir(), "google-calendar.json");
|
|
45318
45498
|
}
|
|
@@ -45425,7 +45605,7 @@ function createGoogleCalendar(deps = {}) {
|
|
|
45425
45605
|
for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
|
|
45426
45606
|
if (pageToken) url.searchParams.set("pageToken", pageToken);
|
|
45427
45607
|
const data = parsed(
|
|
45428
|
-
|
|
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")),
|
|
45429
45609
|
await request(url.href, { headers: { Authorization: `Bearer ${access}` } })
|
|
45430
45610
|
);
|
|
45431
45611
|
result.push(...data.items ?? []);
|
|
@@ -45628,14 +45808,14 @@ function createGoogleCalendar(deps = {}) {
|
|
|
45628
45808
|
close: clearPending2
|
|
45629
45809
|
};
|
|
45630
45810
|
}
|
|
45631
|
-
var import_node_http, import_node_crypto10, import_node_child_process5,
|
|
45811
|
+
var import_node_http, import_node_crypto10, import_node_child_process5, import_zod70, TOKEN_URL, REVOKE_URL, API, tokenSchema, calendarSchema2, eventSchema;
|
|
45632
45812
|
var init_google_calendar = __esm({
|
|
45633
45813
|
"src/command-os/connectors/google-calendar.ts"() {
|
|
45634
45814
|
"use strict";
|
|
45635
45815
|
import_node_http = require("node:http");
|
|
45636
45816
|
import_node_crypto10 = require("node:crypto");
|
|
45637
45817
|
import_node_child_process5 = require("node:child_process");
|
|
45638
|
-
|
|
45818
|
+
import_zod70 = require("zod");
|
|
45639
45819
|
init_credentials_store();
|
|
45640
45820
|
init_agency_profile();
|
|
45641
45821
|
init_account_read_retry();
|
|
@@ -45643,22 +45823,22 @@ var init_google_calendar = __esm({
|
|
|
45643
45823
|
TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
45644
45824
|
REVOKE_URL = "https://oauth2.googleapis.com/revoke";
|
|
45645
45825
|
API = "https://www.googleapis.com/calendar/v3";
|
|
45646
|
-
tokenSchema =
|
|
45647
|
-
calendarSchema2 =
|
|
45648
|
-
id:
|
|
45649
|
-
summary:
|
|
45650
|
-
summaryOverride:
|
|
45651
|
-
primary:
|
|
45652
|
-
timeZone:
|
|
45653
|
-
accessRole:
|
|
45826
|
+
tokenSchema = import_zod70.z.object({ access_token: import_zod70.z.string().min(1), refresh_token: import_zod70.z.string().optional(), scope: import_zod70.z.string().optional() });
|
|
45827
|
+
calendarSchema2 = import_zod70.z.object({
|
|
45828
|
+
id: import_zod70.z.string().min(1),
|
|
45829
|
+
summary: import_zod70.z.string().optional(),
|
|
45830
|
+
summaryOverride: import_zod70.z.string().optional(),
|
|
45831
|
+
primary: import_zod70.z.boolean().optional(),
|
|
45832
|
+
timeZone: import_zod70.z.string().optional(),
|
|
45833
|
+
accessRole: import_zod70.z.enum(["none", "freeBusyReader", "reader", "writer", "owner"]).optional()
|
|
45654
45834
|
});
|
|
45655
|
-
eventSchema =
|
|
45656
|
-
summary:
|
|
45657
|
-
status:
|
|
45658
|
-
start:
|
|
45659
|
-
end:
|
|
45660
|
-
attendees:
|
|
45661
|
-
attendeesOmitted:
|
|
45835
|
+
eventSchema = import_zod70.z.object({
|
|
45836
|
+
summary: import_zod70.z.string().optional(),
|
|
45837
|
+
status: import_zod70.z.string().optional(),
|
|
45838
|
+
start: import_zod70.z.object({ date: import_zod70.z.string().optional(), dateTime: import_zod70.z.string().optional() }),
|
|
45839
|
+
end: import_zod70.z.object({ date: import_zod70.z.string().optional(), dateTime: import_zod70.z.string().optional() }),
|
|
45840
|
+
attendees: import_zod70.z.array(import_zod70.z.object({ email: import_zod70.z.string().optional(), displayName: import_zod70.z.string().optional(), self: import_zod70.z.boolean().optional() })).optional(),
|
|
45841
|
+
attendeesOmitted: import_zod70.z.boolean().optional()
|
|
45662
45842
|
});
|
|
45663
45843
|
}
|
|
45664
45844
|
});
|
|
@@ -45669,7 +45849,7 @@ function registerGoogleCalendarTools(server2, calendar = createGoogleCalendar())
|
|
|
45669
45849
|
server2,
|
|
45670
45850
|
"connect_google_calendar",
|
|
45671
45851
|
"Connect your own Google Calendar with read-only access. The first run opens Google sign-in and saves nothing. The second run finishes after reading your calendars. Use status when asked what is connected. Use disconnect to remove the local sign-in and revoke it with Google. No tool shows a secret.",
|
|
45672
|
-
{ action:
|
|
45852
|
+
{ action: import_zod71.z.enum(["start", "finish", "status", "disconnect"]).optional().describe("Use start to open sign-in. Use finish after browser sign-in. Use status to show only the last four characters. Use disconnect to remove and revoke access.") },
|
|
45673
45853
|
async ({ action }) => calendar.connect(action)
|
|
45674
45854
|
);
|
|
45675
45855
|
safeTool(
|
|
@@ -45677,18 +45857,18 @@ function registerGoogleCalendarTools(server2, calendar = createGoogleCalendar())
|
|
|
45677
45857
|
"read_my_calendar",
|
|
45678
45858
|
"Read today or the week ahead from your own Google Calendar. Each entry shows its time in your timezone and the other people attending. All-day entries say all day. An empty window says so. Your primary calendar is read unless you name another one. For a morning brief, show this as a separate personal calendar section and never count these entries as client appointments.",
|
|
45679
45859
|
{
|
|
45680
|
-
days:
|
|
45681
|
-
calendar:
|
|
45682
|
-
timezone:
|
|
45860
|
+
days: import_zod71.z.number().int().min(1).max(31).optional().describe("Use 1 for today. Use 7 for the week ahead. Use 3 for the next three days."),
|
|
45861
|
+
calendar: import_zod71.z.string().min(1).optional().describe("The calendar name or identifier. Defaults to your primary calendar."),
|
|
45862
|
+
timezone: import_zod71.z.string().min(1).optional().describe("Your IANA timezone, such as America/Phoenix. Defaults to your agency timezone, then your primary Google Calendar timezone, then this machine's timezone.")
|
|
45683
45863
|
},
|
|
45684
45864
|
async (options) => calendar.read(options)
|
|
45685
45865
|
);
|
|
45686
45866
|
}
|
|
45687
|
-
var
|
|
45867
|
+
var import_zod71;
|
|
45688
45868
|
var init_google_calendar2 = __esm({
|
|
45689
45869
|
"src/tools/google-calendar.ts"() {
|
|
45690
45870
|
"use strict";
|
|
45691
|
-
|
|
45871
|
+
import_zod71 = require("zod");
|
|
45692
45872
|
init_tool_helpers();
|
|
45693
45873
|
init_google_calendar();
|
|
45694
45874
|
}
|
|
@@ -45917,12 +46097,12 @@ function registerDeliveryAuditTools(server2) {
|
|
|
45917
46097
|
"audit_my_delivery",
|
|
45918
46098
|
"Audit your OWN agency: how many hours a month each recurring job costs you, ranked worst first, with the ones that can already be taken off you named. Call it with nothing to see the questions; call it again with answers as you get them \u2014 they are saved, so you can finish it over several sittings. A job you cannot put minutes against is named as needing measuring and is never estimated.",
|
|
45919
46099
|
{
|
|
45920
|
-
answers:
|
|
45921
|
-
minutes:
|
|
45922
|
-
timesPerMonth:
|
|
46100
|
+
answers: import_zod72.z.record(import_zod72.z.string(), import_zod72.z.object({
|
|
46101
|
+
minutes: import_zod72.z.union([import_zod72.z.number(), import_zod72.z.string()]).optional().describe("Minutes for ONE instance of the job. 'unknown' is a real answer and is recorded as one."),
|
|
46102
|
+
timesPerMonth: import_zod72.z.union([import_zod72.z.number(), import_zod72.z.string()]).optional().describe("How many times a month it happens.")
|
|
45923
46103
|
})).optional().describe("Keyed by task id, from the questions this tool returns. Merges with what is already saved."),
|
|
45924
|
-
available:
|
|
45925
|
-
reset:
|
|
46104
|
+
available: import_zod72.z.record(import_zod72.z.string(), import_zod72.z.boolean()).optional().describe("Which tools this operator actually has, if it is not all of them. Omit on a normal licence."),
|
|
46105
|
+
reset: import_zod72.z.boolean().optional().describe("Throw away the saved answers and start the audit again. Only on an explicit ask.")
|
|
45926
46106
|
},
|
|
45927
46107
|
async ({ answers, available, reset }) => {
|
|
45928
46108
|
if (reset) resetDeliveryAudit();
|
|
@@ -45964,11 +46144,11 @@ function registerDeliveryAuditTools(server2) {
|
|
|
45964
46144
|
}
|
|
45965
46145
|
);
|
|
45966
46146
|
}
|
|
45967
|
-
var
|
|
46147
|
+
var import_zod72, tasks, areas, areaLabel;
|
|
45968
46148
|
var init_delivery_audit = __esm({
|
|
45969
46149
|
"src/tools/delivery-audit.ts"() {
|
|
45970
46150
|
"use strict";
|
|
45971
|
-
|
|
46151
|
+
import_zod72 = require("zod");
|
|
45972
46152
|
init_tool_helpers();
|
|
45973
46153
|
init_audit();
|
|
45974
46154
|
init_store4();
|
|
@@ -46311,20 +46491,20 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46311
46491
|
"plan_automation",
|
|
46312
46492
|
"Plan one of YOUR OWN automations \u2014 something specific to your agency, built out of the tools you already have. Give it the goal, what you found when you looked at the account (every id you are going to touch), and the steps. It refuses a plan that targets an id nobody looked at, a plan whose steps cannot run in any order, and a step with no reason on it. It writes nothing: it produces a plan you read and approve.",
|
|
46313
46493
|
{
|
|
46314
|
-
goal:
|
|
46315
|
-
findings:
|
|
46316
|
-
kind:
|
|
46317
|
-
id:
|
|
46318
|
-
name:
|
|
46319
|
-
observedAt:
|
|
46494
|
+
goal: import_zod73.z.string().min(4).describe("What this build is for, in your own words."),
|
|
46495
|
+
findings: import_zod73.z.array(import_zod73.z.object({
|
|
46496
|
+
kind: import_zod73.z.string().describe("pipeline, custom_field, workflow, calendar \u2014 whatever it is."),
|
|
46497
|
+
id: import_zod73.z.string().describe("The real id, as the account reported it."),
|
|
46498
|
+
name: import_zod73.z.string().optional(),
|
|
46499
|
+
observedAt: import_zod73.z.string().optional().describe("When you looked. Defaults to now. Anything older than an hour is treated as not having looked.")
|
|
46320
46500
|
})).describe("What you found when you explored. Every existing id a step targets has to be in here."),
|
|
46321
|
-
steps:
|
|
46322
|
-
id:
|
|
46323
|
-
tool:
|
|
46324
|
-
why:
|
|
46325
|
-
targets:
|
|
46326
|
-
creates:
|
|
46327
|
-
args:
|
|
46501
|
+
steps: import_zod73.z.array(import_zod73.z.object({
|
|
46502
|
+
id: import_zod73.z.string().describe("Short, unique within this plan."),
|
|
46503
|
+
tool: import_zod73.z.string().describe("The tool that does it."),
|
|
46504
|
+
why: import_zod73.z.string().min(3).describe("Plain English. This is what the operator reads before approving."),
|
|
46505
|
+
targets: import_zod73.z.array(targetSchema).default([]),
|
|
46506
|
+
creates: import_zod73.z.array(import_zod73.z.object({ ref: import_zod73.z.string(), kind: import_zod73.z.string() })).optional().describe("What this step brings into existence, for later steps to point at."),
|
|
46507
|
+
args: import_zod73.z.record(import_zod73.z.string(), import_zod73.z.unknown()).optional().describe("The arguments, kept verbatim \u2014 the approval covers these, so changing one voids it.")
|
|
46328
46508
|
})).min(1)
|
|
46329
46509
|
},
|
|
46330
46510
|
async ({ goal, findings, steps }) => {
|
|
@@ -46368,9 +46548,9 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46368
46548
|
"approve_automation",
|
|
46369
46549
|
"Record that the OPERATOR has read this exact plan and approved it. Requires the plan's fingerprint: if a single argument changed since they read it, the approval is refused, because it covered a different document. Nothing may be recorded as applied until this has been done.",
|
|
46370
46550
|
{
|
|
46371
|
-
buildId:
|
|
46372
|
-
planId:
|
|
46373
|
-
approvedBy:
|
|
46551
|
+
buildId: import_zod73.z.string(),
|
|
46552
|
+
planId: import_zod73.z.string().describe("The fingerprint from plan_automation. It must match the plan as it stands now."),
|
|
46553
|
+
approvedBy: import_zod73.z.string().describe("Who approved it. Their name, not yours.")
|
|
46374
46554
|
},
|
|
46375
46555
|
async ({ buildId, planId, approvedBy }) => {
|
|
46376
46556
|
const a = readAutomation(buildId);
|
|
@@ -46388,14 +46568,14 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46388
46568
|
"record_automation_step",
|
|
46389
46569
|
"Record what happened when a step was run: applied, with how to undo it, or failed, with what came back. A step cannot be recorded as done without its undo \u2014 a write with no way back is the one thing this refuses \u2014 and a step cannot be recorded before the thing it depends on has run. A step that FAILED can be run again once you have fixed what stopped it: record the second attempt with retry:true and both attempts are kept.",
|
|
46390
46570
|
{
|
|
46391
|
-
buildId:
|
|
46392
|
-
stepId:
|
|
46393
|
-
state:
|
|
46394
|
-
undo:
|
|
46395
|
-
because:
|
|
46396
|
-
result:
|
|
46397
|
-
resolved:
|
|
46398
|
-
retry:
|
|
46571
|
+
buildId: import_zod73.z.string(),
|
|
46572
|
+
stepId: import_zod73.z.string(),
|
|
46573
|
+
state: import_zod73.z.enum(["applied", "failed"]),
|
|
46574
|
+
undo: import_zod73.z.string().optional().describe("Required on 'applied'. How to reverse this exact write, specifically enough to act on months later."),
|
|
46575
|
+
because: import_zod73.z.string().optional().describe("Required on 'failed'. What the account actually said."),
|
|
46576
|
+
result: import_zod73.z.unknown().optional().describe("What came back, kept so a stall can be diagnosed without re-running anything."),
|
|
46577
|
+
resolved: import_zod73.z.record(import_zod73.z.string(), import_zod73.z.string()).optional().describe("The real ids this step created, keyed by the ref later steps use."),
|
|
46578
|
+
retry: import_zod73.z.boolean().optional().describe("This is a second run of a step that failed. Both attempts are kept, and the earlier failure stops blocking what came after it.")
|
|
46399
46579
|
},
|
|
46400
46580
|
async ({ buildId, stepId, state, undo, because, result, resolved: resolved2, retry }) => {
|
|
46401
46581
|
const a = readAutomation(buildId);
|
|
@@ -46418,8 +46598,8 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46418
46598
|
"abandon_automation",
|
|
46419
46599
|
"Stop a build for good. It does NOT undo anything and does not pretend to: it hands you back everything that is still applied to the account, so abandoning the plan and cleaning up after it stay two separate acts. A build abandoned with no reason recorded is one somebody re-plans identically next month, so a reason is required.",
|
|
46420
46600
|
{
|
|
46421
|
-
buildId:
|
|
46422
|
-
because:
|
|
46601
|
+
buildId: import_zod73.z.string(),
|
|
46602
|
+
because: import_zod73.z.string().min(3).describe("Why it is being stopped. Kept with the build.")
|
|
46423
46603
|
},
|
|
46424
46604
|
async ({ buildId, because }) => {
|
|
46425
46605
|
const a = readAutomation(buildId);
|
|
@@ -46439,7 +46619,7 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46439
46619
|
server2,
|
|
46440
46620
|
"automation_status",
|
|
46441
46621
|
"Where your own builds actually are. A build that stopped is reported as STALLED, naming the step and what the account said \u2014 never as a percentage, because a build that is '70% complete' is one somebody lives with for three weeks. Also gives you the undo list, newest first.",
|
|
46442
|
-
{ buildId:
|
|
46622
|
+
{ buildId: import_zod73.z.string().optional().describe("One build. Omit for all of them.") },
|
|
46443
46623
|
async ({ buildId }) => {
|
|
46444
46624
|
const all = readAutomations();
|
|
46445
46625
|
const ids = buildId ? [buildId] : Object.keys(all);
|
|
@@ -46470,17 +46650,17 @@ function registerAgencyBuildTools(server2, knownTools) {
|
|
|
46470
46650
|
}
|
|
46471
46651
|
);
|
|
46472
46652
|
}
|
|
46473
|
-
var
|
|
46653
|
+
var import_zod73, targetSchema;
|
|
46474
46654
|
var init_agency_build = __esm({
|
|
46475
46655
|
"src/tools/agency-build.ts"() {
|
|
46476
46656
|
"use strict";
|
|
46477
|
-
|
|
46657
|
+
import_zod73 = require("zod");
|
|
46478
46658
|
init_tool_helpers();
|
|
46479
46659
|
init_automation();
|
|
46480
46660
|
init_store5();
|
|
46481
|
-
targetSchema =
|
|
46482
|
-
|
|
46483
|
-
|
|
46661
|
+
targetSchema = import_zod73.z.union([
|
|
46662
|
+
import_zod73.z.object({ existing: import_zod73.z.string().describe("A real id, which must appear in `findings`."), kind: import_zod73.z.string() }),
|
|
46663
|
+
import_zod73.z.object({ ref: import_zod73.z.string().describe("Something an earlier step in this plan creates."), kind: import_zod73.z.string() })
|
|
46484
46664
|
]);
|
|
46485
46665
|
}
|
|
46486
46666
|
});
|
|
@@ -46654,7 +46834,7 @@ function registerClientEngagementTools(server2, registry2) {
|
|
|
46654
46834
|
server2,
|
|
46655
46835
|
"get_income_sources",
|
|
46656
46836
|
"Read every source of income you have recorded \u2014 clients whose GoHighLevel accounts you run, software subscribers, event fees, revenue shares \u2014 with the offer they are on, what they pay, monthly or one-time, when it started, when it renews, who runs the account, and any SOPs specific to them. Use this before quoting, renewing, writing a proposal or planning a client's week, instead of asking the user again. Also returns your recurring revenue as a FLOOR (the sum of everything active and monthly that has a price recorded, whatever kind of income it is) plus a count of the ones that do not, because a total assembled from partial data is not a total. Pass locationId for one record, or nothing for all of them. The locationId in the reply is for calling set_income_source \u2014 refer to people and companies by name when you answer the user, never by id. Local file on this machine; never touches GoHighLevel.",
|
|
46657
|
-
{ locationId:
|
|
46837
|
+
{ locationId: import_zod74.z.string().optional().describe("One record's id \u2014 a sub-account id for a client, or the key you recorded other income under. Omit for the whole roster.") },
|
|
46658
46838
|
async ({ locationId: locationId2 }) => {
|
|
46659
46839
|
const profile = readAgencyProfile();
|
|
46660
46840
|
const saved = readClientEngagements();
|
|
@@ -46700,32 +46880,32 @@ function registerClientEngagementTools(server2, registry2) {
|
|
|
46700
46880
|
"set_income_source",
|
|
46701
46881
|
"Record or update ONE source of income \u2014 a client whose GoHighLevel account you run, a software subscriber, an event fee \u2014 with its offer, price, monthly or one-time, start and renewal dates, status, who runs the account, and SOPs specific to it. Send only the fields you are changing; anything you omit is left exactly as it was, so this is safe to call repeatedly. To CLEAR a field pass null for it, and to remove the record entirely pass remove:true. Dates are YYYY-MM-DD. Local file on this machine; never touches GoHighLevel.",
|
|
46702
46882
|
{
|
|
46703
|
-
locationId:
|
|
46883
|
+
locationId: import_zod74.z.string().optional().describe("The client's GHL sub-account id \u2014 the same id the board uses. Use this for a client whose GoHighLevel account you run. For income with no sub-account, use `key` instead."),
|
|
46704
46884
|
// WIDENED 2026-09-01 (Jerry: "different clients pay for different things.
|
|
46705
46885
|
// My income comes from a variety of sources"). Two argument names for one
|
|
46706
46886
|
// slot, rather than one argument whose name lies about half its uses: a
|
|
46707
46887
|
// Stripe-only software subscriber has no locationId, and calling their
|
|
46708
46888
|
// Stripe customer id a "locationId" is the kind of description-contradicts-
|
|
46709
46889
|
// the-code defect this product keeps paying for.
|
|
46710
|
-
key:
|
|
46711
|
-
kind:
|
|
46890
|
+
key: import_zod74.z.string().optional().describe("Identifier for income that is NOT a GoHighLevel account you run \u2014 a software subscriber, an event fee, a revenue share. Anything stable and unique that is NOT one of your GoHighLevel account ids; a Stripe customer id is ideal because it makes the Stripe pairing automatic. Send this instead of locationId, never both."),
|
|
46891
|
+
kind: import_zod74.z.enum(["client", "software", "other"]).nullable().optional().describe("What kind of income this is. 'client' (the default) = you run a GoHighLevel account for them, so they belong on the build board and are measured against your price floor. 'software' = they bought a licence or subscription. 'other' = events, revenue share, training, referrals. Only 'client' ever appears as a build."),
|
|
46712
46892
|
// Every editable field is NULLABLE, not just optional. The description
|
|
46713
46893
|
// promises "pass null to clear", and a schema that rejects null would make
|
|
46714
46894
|
// that promise a lie the tests never caught, because they cleared fields by
|
|
46715
46895
|
// calling the store directly instead of through this surface (Codex,
|
|
46716
46896
|
// 2026-08-28). Absent = untouched; null = clear; a value = set.
|
|
46717
|
-
clientName:
|
|
46718
|
-
offer:
|
|
46719
|
-
price:
|
|
46720
|
-
cadence:
|
|
46721
|
-
currency:
|
|
46722
|
-
startedOn:
|
|
46723
|
-
renewsOn:
|
|
46724
|
-
status:
|
|
46725
|
-
owner:
|
|
46726
|
-
sops:
|
|
46727
|
-
notes:
|
|
46728
|
-
remove:
|
|
46897
|
+
clientName: import_zod74.z.string().nullable().optional().describe("How you refer to them, if it differs from the account name."),
|
|
46898
|
+
offer: import_zod74.z.string().nullable().optional().describe("Which of your offers they are on. Matched against your agency profile so a typo is reported."),
|
|
46899
|
+
price: import_zod74.z.number().min(0).nullable().optional().describe("What they actually pay \u2014 not your floor."),
|
|
46900
|
+
cadence: import_zod74.z.enum(["one-time", "monthly"]).nullable().optional(),
|
|
46901
|
+
currency: import_zod74.z.string().nullable().optional(),
|
|
46902
|
+
startedOn: import_zod74.z.string().nullable().optional().describe("YYYY-MM-DD."),
|
|
46903
|
+
renewsOn: import_zod74.z.string().nullable().optional().describe("YYYY-MM-DD."),
|
|
46904
|
+
status: import_zod74.z.enum(["active", "paused", "ended"]).nullable().optional(),
|
|
46905
|
+
owner: import_zod74.z.string().nullable().optional().describe("Which staff member runs this account."),
|
|
46906
|
+
sops: import_zod74.z.array(import_zod74.z.string()).nullable().optional().describe("SOPs that apply to this one only. Leave empty to use your agency's."),
|
|
46907
|
+
notes: import_zod74.z.string().nullable().optional(),
|
|
46908
|
+
remove: import_zod74.z.boolean().optional().describe("Delete this record entirely.")
|
|
46729
46909
|
},
|
|
46730
46910
|
async (args) => {
|
|
46731
46911
|
const { locationId: locationId2, key, remove, ...rest } = args;
|
|
@@ -46775,7 +46955,7 @@ function registerClientEngagementTools(server2, registry2) {
|
|
|
46775
46955
|
server2,
|
|
46776
46956
|
"client_this_week",
|
|
46777
46957
|
"Answer 'what should I be doing for this client this week' for ONE client, from what you already know: what they bought, where their build has stopped, what is waiting on you versus already in motion, a renewal that is close, whether the price is under your own floor, and the SOP that applies. Names the client by name or by account id. Everything it cannot know is listed plainly rather than guessed. Local files on this machine; never touches GoHighLevel.",
|
|
46778
|
-
{ client:
|
|
46958
|
+
{ client: import_zod74.z.string().describe("The client's name as you refer to them, or their sub-account id.") },
|
|
46779
46959
|
async ({ client }) => {
|
|
46780
46960
|
const state = readBoardState();
|
|
46781
46961
|
const roster = knownClients(registry2, state);
|
|
@@ -46795,11 +46975,11 @@ function registerClientEngagementTools(server2, registry2) {
|
|
|
46795
46975
|
}
|
|
46796
46976
|
);
|
|
46797
46977
|
}
|
|
46798
|
-
var
|
|
46978
|
+
var import_zod74;
|
|
46799
46979
|
var init_client_engagements2 = __esm({
|
|
46800
46980
|
"src/tools/client-engagements.ts"() {
|
|
46801
46981
|
"use strict";
|
|
46802
|
-
|
|
46982
|
+
import_zod74 = require("zod");
|
|
46803
46983
|
init_tool_helpers();
|
|
46804
46984
|
init_agency_profile();
|
|
46805
46985
|
init_client_engagements();
|
|
@@ -47259,14 +47439,14 @@ function registerAuditReportTools(server2) {
|
|
|
47259
47439
|
"build_audit_report",
|
|
47260
47440
|
"Turn the output of `audit_workflows` into a client-ready assessment: plain-English findings with money attached where it can honestly be attached, ranked worst-first, plus an explicit list of what could NOT be checked. Call `audit_workflows` first and pass its result here \u2014 this does not re-read the account. Give `leadsPerMonth`, `closeRate` and `monthlyValuePerClient` if you know them and the findings get costed in dollars; leave them out and it says which input is missing instead of guessing. `monthlyValuePerClient` defaults to your saved minimum monthly fee from your agency profile. Use this to produce the document you send or present, not to diagnose \u2014 the diagnosing already happened.",
|
|
47261
47441
|
{
|
|
47262
|
-
audit:
|
|
47263
|
-
audience:
|
|
47264
|
-
accountName:
|
|
47265
|
-
locationId:
|
|
47266
|
-
leadsPerMonth:
|
|
47267
|
-
closeRate:
|
|
47268
|
-
monthlyValuePerClient:
|
|
47269
|
-
generatedAt:
|
|
47442
|
+
audit: import_zod75.z.record(import_zod75.z.string(), import_zod75.z.unknown()).describe("The full JSON result returned by audit_workflows."),
|
|
47443
|
+
audience: import_zod75.z.enum(["internal", "prospect"]).optional().describe("Who reads it. 'internal' (default) is a report about an account you already run, and may use your own minimum monthly as what a client is worth. 'prospect' is a document about somebody else's business: your numbers are NEVER used, so findings stay uncosted unless you supply THEIR lead volume, close rate and client value."),
|
|
47444
|
+
accountName: import_zod75.z.string().describe("The client's name as they should see it on the document."),
|
|
47445
|
+
locationId: import_zod75.z.string().optional().describe("Sub-account id. Recorded on the document, never shown in the body."),
|
|
47446
|
+
leadsPerMonth: import_zod75.z.number().min(0).optional().describe("Leads this account actually receives per month, if known. Without it, findings are not costed in dollars."),
|
|
47447
|
+
closeRate: import_zod75.z.number().min(0).max(1).optional().describe("Share of reached leads that become clients, 0-1. Never guessed."),
|
|
47448
|
+
monthlyValuePerClient: import_zod75.z.number().min(0).optional().describe("What one new client is worth per month. Defaults to your agency profile's minimum monthly fee."),
|
|
47449
|
+
generatedAt: import_zod75.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
|
|
47270
47450
|
},
|
|
47271
47451
|
async (args) => {
|
|
47272
47452
|
const a = args;
|
|
@@ -47324,12 +47504,12 @@ function registerAuditReportTools(server2) {
|
|
|
47324
47504
|
"build_site_audit_report",
|
|
47325
47505
|
"Turn a website read-through into the assessment document you put in front of a PROSPECT \u2014 someone whose GoHighLevel account you do not have. Pass the verified observations from a site crawl and it returns plain-English findings, ranked, each one carrying the quote and the page it came from. Use it before a sales call, or alongside a receptionist demo, so the same read of their site fills both. It never invents money: a website cannot tell you what a lead is worth to that business, so findings come back uncosted unless you supply THEIR figures, and the document says why. It also never repeats a claim the crawl could not stand up \u2014 anything refused is returned separately, so you can see what was thrown out rather than wonder what was missed.",
|
|
47326
47506
|
{
|
|
47327
|
-
observations:
|
|
47328
|
-
businessName:
|
|
47329
|
-
leadsPerMonth:
|
|
47330
|
-
closeRate:
|
|
47331
|
-
monthlyValuePerClient:
|
|
47332
|
-
generatedAt:
|
|
47507
|
+
observations: import_zod75.z.record(import_zod75.z.string(), import_zod75.z.unknown()).describe("The verified observations payload from the site read: site, pagesRead, observations, limits. Passed through as-is."),
|
|
47508
|
+
businessName: import_zod75.z.string().describe("The business's name as it should appear on the document."),
|
|
47509
|
+
leadsPerMonth: import_zod75.z.number().min(0).optional().describe("THEIR leads per month, if they told you. Never your own figure."),
|
|
47510
|
+
closeRate: import_zod75.z.number().min(0).max(1).optional().describe("THEIR close rate, 0-1, if they told you. Never guessed."),
|
|
47511
|
+
monthlyValuePerClient: import_zod75.z.number().min(0).optional().describe("What one client is worth to THEM per month, if they told you."),
|
|
47512
|
+
generatedAt: import_zod75.z.string().optional().describe("ISO timestamp for the document. Defaults to now.")
|
|
47333
47513
|
},
|
|
47334
47514
|
async (args) => {
|
|
47335
47515
|
const a = args;
|
|
@@ -47388,11 +47568,11 @@ function registerAuditReportTools(server2) {
|
|
|
47388
47568
|
}
|
|
47389
47569
|
);
|
|
47390
47570
|
}
|
|
47391
|
-
var
|
|
47571
|
+
var import_zod75;
|
|
47392
47572
|
var init_audit_report2 = __esm({
|
|
47393
47573
|
"src/tools/audit-report.ts"() {
|
|
47394
47574
|
"use strict";
|
|
47395
|
-
|
|
47575
|
+
import_zod75 = require("zod");
|
|
47396
47576
|
init_tool_helpers();
|
|
47397
47577
|
init_audit_report();
|
|
47398
47578
|
init_audit_translate();
|
|
@@ -47660,9 +47840,9 @@ function registerDailyBriefTools(server2, registry2) {
|
|
|
47660
47840
|
"daily_brief",
|
|
47661
47841
|
"The morning brief across ALL your clients: today's appointments first, then what is waiting on YOU, what is already running, renewals inside 30 days, clients PAYING YOU WHO HAVE BOOKED NOTHING IN THREE WEEKS, clients under your own price floor, and new leads overnight. Use it to answer 'what do I need to do today' or 'anything I'm missing' without opening a single sub-account. Reads today's calendar and new-contact counts live from each connected account; pass live=false for records only. Accounts that could not be read are named with the reason \u2014 it never reports an account as quiet when it simply could not get in.",
|
|
47662
47842
|
{
|
|
47663
|
-
live:
|
|
47664
|
-
hoursBack:
|
|
47665
|
-
quietAfterDays:
|
|
47843
|
+
live: import_zod76.z.boolean().optional().describe("Read today's calendars and lead counts from the connected accounts. Default true. false answers from local records alone."),
|
|
47844
|
+
hoursBack: import_zod76.z.number().min(1).max(72).optional().describe("Count new leads over this many hours instead of since midnight."),
|
|
47845
|
+
quietAfterDays: import_zod76.z.number().min(1).max(180).optional().describe("How many days a paying client has to book nothing before the brief says so. Defaults to 21.")
|
|
47666
47846
|
},
|
|
47667
47847
|
async ({ live, hoursBack, quietAfterDays }) => {
|
|
47668
47848
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -47699,11 +47879,11 @@ function registerDailyBriefTools(server2, registry2) {
|
|
|
47699
47879
|
}
|
|
47700
47880
|
);
|
|
47701
47881
|
}
|
|
47702
|
-
var
|
|
47882
|
+
var import_zod76;
|
|
47703
47883
|
var init_daily_brief2 = __esm({
|
|
47704
47884
|
"src/tools/daily-brief.ts"() {
|
|
47705
47885
|
"use strict";
|
|
47706
|
-
|
|
47886
|
+
import_zod76 = require("zod");
|
|
47707
47887
|
init_tool_helpers();
|
|
47708
47888
|
init_ghl_client();
|
|
47709
47889
|
init_agency_profile();
|
|
@@ -47756,7 +47936,7 @@ function registerSnapshotTools(server2, client, registry2) {
|
|
|
47756
47936
|
"list_snapshots",
|
|
47757
47937
|
"List the agency's GHL snapshots (id, name, type) so you can pick the right one by name before applying it to a sub-account. Reads the agency/company-scoped key (not a sub-account PIT). companyId defaults to the active location's company; pass it explicitly to target a different company you have the agency key for. Read-only.",
|
|
47758
47938
|
{
|
|
47759
|
-
companyId:
|
|
47939
|
+
companyId: import_zod77.z.string().optional().describe(
|
|
47760
47940
|
"Agency/company ID whose snapshots to list. Defaults to the active location's company. Must match the company your agency key is scoped to."
|
|
47761
47941
|
)
|
|
47762
47942
|
},
|
|
@@ -47781,11 +47961,11 @@ function registerSnapshotTools(server2, client, registry2) {
|
|
|
47781
47961
|
"create_snapshot_share_link",
|
|
47782
47962
|
"Create a shareable load link for one of the agency's snapshots (returns a gohighlevel.com/?share=... URL to import the snapshot into a sub-account). Uses the agency/company-scoped key. WARNING: this is NOT idempotent \u2014 each call mints a NEW link, and there is no API to list or revoke links (revoke in the GHL UI under the snapshot's share settings). Pick share_type deliberately. Use list_snapshots first to get the snapshot id.",
|
|
47783
47963
|
{
|
|
47784
|
-
snapshot_id:
|
|
47785
|
-
share_type:
|
|
47964
|
+
snapshot_id: import_zod77.z.string().describe("The snapshot id to share (from list_snapshots)."),
|
|
47965
|
+
share_type: import_zod77.z.enum(SHARE_TYPES).describe(
|
|
47786
47966
|
"Share link type. 'link' = standard share link; 'permanent_link' = non-expiring; 'agency_link' = share to agencies; 'location_link' = load into a sub-account/location; 'marketplace_link' = marketplace listing. No default \u2014 choose intentionally."
|
|
47787
47967
|
),
|
|
47788
|
-
companyId:
|
|
47968
|
+
companyId: import_zod77.z.string().optional().describe(
|
|
47789
47969
|
"Agency/company ID that owns the snapshot. Defaults to the active location's company. Must match the company your agency key is scoped to."
|
|
47790
47970
|
)
|
|
47791
47971
|
},
|
|
@@ -47813,16 +47993,16 @@ function registerSnapshotTools(server2, client, registry2) {
|
|
|
47813
47993
|
}
|
|
47814
47994
|
);
|
|
47815
47995
|
}
|
|
47816
|
-
var
|
|
47996
|
+
var import_zod77, SnapshotSchema, SnapshotsResponseSchema, ShareLinkResponseSchema, SHARE_TYPES;
|
|
47817
47997
|
var init_snapshots = __esm({
|
|
47818
47998
|
"src/tools/snapshots.ts"() {
|
|
47819
47999
|
"use strict";
|
|
47820
|
-
|
|
48000
|
+
import_zod77 = require("zod");
|
|
47821
48001
|
init_ghl_client();
|
|
47822
48002
|
init_tool_helpers();
|
|
47823
|
-
SnapshotSchema =
|
|
47824
|
-
SnapshotsResponseSchema =
|
|
47825
|
-
ShareLinkResponseSchema =
|
|
48003
|
+
SnapshotSchema = import_zod77.z.object({ id: import_zod77.z.string(), name: import_zod77.z.string(), type: import_zod77.z.string() }).passthrough();
|
|
48004
|
+
SnapshotsResponseSchema = import_zod77.z.object({ snapshots: import_zod77.z.array(SnapshotSchema) }).passthrough();
|
|
48005
|
+
ShareLinkResponseSchema = import_zod77.z.object({ id: import_zod77.z.string(), shareLink: import_zod77.z.string() }).passthrough();
|
|
47826
48006
|
SHARE_TYPES = [
|
|
47827
48007
|
"link",
|
|
47828
48008
|
"permanent_link",
|
|
@@ -47840,7 +48020,7 @@ function registerPhoneTools(server2, client) {
|
|
|
47840
48020
|
"list_phone_numbers",
|
|
47841
48021
|
"List the LC Phone numbers provisioned for a location (sid, number, label). Read-only. Use to verify or count purchased numbers (e.g. provisioning step 11). Number purchase is not exposed (billable write).",
|
|
47842
48022
|
{
|
|
47843
|
-
locationId:
|
|
48023
|
+
locationId: import_zod78.z.string().optional().describe("Defaults to the active location.")
|
|
47844
48024
|
},
|
|
47845
48025
|
async ({ locationId: locationId2 }) => {
|
|
47846
48026
|
const loc = client.resolveLocationId(locationId2);
|
|
@@ -47858,7 +48038,7 @@ function registerPhoneTools(server2, client) {
|
|
|
47858
48038
|
"list_number_pools",
|
|
47859
48039
|
"List LC Phone number pools configured for a location. Read-only.",
|
|
47860
48040
|
{
|
|
47861
|
-
locationId:
|
|
48041
|
+
locationId: import_zod78.z.string().optional().describe("Defaults to the active location.")
|
|
47862
48042
|
},
|
|
47863
48043
|
async ({ locationId: locationId2 }) => {
|
|
47864
48044
|
const loc = client.resolveLocationId(locationId2);
|
|
@@ -47868,15 +48048,15 @@ function registerPhoneTools(server2, client) {
|
|
|
47868
48048
|
}
|
|
47869
48049
|
);
|
|
47870
48050
|
}
|
|
47871
|
-
var
|
|
48051
|
+
var import_zod78, PhoneNumberSchema, NumbersResponseSchema, PoolsResponseSchema;
|
|
47872
48052
|
var init_phone = __esm({
|
|
47873
48053
|
"src/tools/phone.ts"() {
|
|
47874
48054
|
"use strict";
|
|
47875
|
-
|
|
48055
|
+
import_zod78 = require("zod");
|
|
47876
48056
|
init_tool_helpers();
|
|
47877
|
-
PhoneNumberSchema =
|
|
47878
|
-
NumbersResponseSchema =
|
|
47879
|
-
PoolsResponseSchema =
|
|
48057
|
+
PhoneNumberSchema = import_zod78.z.object({ sid: import_zod78.z.string(), value: import_zod78.z.string(), title: import_zod78.z.string().optional() }).passthrough();
|
|
48058
|
+
NumbersResponseSchema = import_zod78.z.object({ phoneNumbers: import_zod78.z.array(PhoneNumberSchema) }).passthrough();
|
|
48059
|
+
PoolsResponseSchema = import_zod78.z.object({ pools: import_zod78.z.array(import_zod78.z.object({}).passthrough()) }).passthrough();
|
|
47880
48060
|
}
|
|
47881
48061
|
});
|
|
47882
48062
|
|
|
@@ -47894,8 +48074,8 @@ function registerAccountHealthTools(server2, client) {
|
|
|
47894
48074
|
"get_account_health_summary",
|
|
47895
48075
|
"Account-health summary for a location, composed from existing reads (GHL has no reporting API). Returns: total contacts + NEW contacts in the window; total opportunities + counts by status (open/won/lost/abandoned); total conversations; phone-number count. Every metric is explicitly labeled all_time vs window (with start/end) \u2014 windowed and all-time numbers are never conflated. Sections that can't be read return status:'unavailable' (never a misleading 0). Revenue and appointments are intentionally excluded (not reachable / too costly via the public API).",
|
|
47896
48076
|
{
|
|
47897
|
-
locationId:
|
|
47898
|
-
windowDays:
|
|
48077
|
+
locationId: import_zod79.z.string().optional().describe("Defaults to the active location."),
|
|
48078
|
+
windowDays: import_zod79.z.number().int().positive().max(365).optional().describe("Lookback window in days for windowed metrics (new contacts). Default 30.")
|
|
47899
48079
|
},
|
|
47900
48080
|
async ({ locationId: locationId2, windowDays }) => {
|
|
47901
48081
|
const loc = client.resolveLocationId(locationId2);
|
|
@@ -47959,16 +48139,16 @@ function registerAccountHealthTools(server2, client) {
|
|
|
47959
48139
|
}
|
|
47960
48140
|
);
|
|
47961
48141
|
}
|
|
47962
|
-
var
|
|
48142
|
+
var import_zod79, MetaTotalSchema, TotalSchema2, NumbersSchema, OPP_STATUSES;
|
|
47963
48143
|
var init_account_health = __esm({
|
|
47964
48144
|
"src/tools/account-health.ts"() {
|
|
47965
48145
|
"use strict";
|
|
47966
|
-
|
|
48146
|
+
import_zod79 = require("zod");
|
|
47967
48147
|
init_tool_helpers();
|
|
47968
48148
|
init_contact_window();
|
|
47969
|
-
MetaTotalSchema =
|
|
47970
|
-
TotalSchema2 =
|
|
47971
|
-
NumbersSchema =
|
|
48149
|
+
MetaTotalSchema = import_zod79.z.object({ meta: import_zod79.z.object({ total: import_zod79.z.number() }).passthrough() }).passthrough();
|
|
48150
|
+
TotalSchema2 = import_zod79.z.object({ total: import_zod79.z.number() }).passthrough();
|
|
48151
|
+
NumbersSchema = import_zod79.z.object({ phoneNumbers: import_zod79.z.array(import_zod79.z.unknown()) }).passthrough();
|
|
47972
48152
|
OPP_STATUSES = ["open", "won", "lost", "abandoned"];
|
|
47973
48153
|
}
|
|
47974
48154
|
});
|
|
@@ -48161,11 +48341,11 @@ function validateBrief(input) {
|
|
|
48161
48341
|
warnings: []
|
|
48162
48342
|
};
|
|
48163
48343
|
}
|
|
48164
|
-
var
|
|
48344
|
+
var import_zod80, BRIEF_SCHEMA_VERSION, PRESETS, presetSchema, BRIEF_SOURCES, briefSourceSchema, pricePointSchema, staffMemberSchema, BRIEF_CALENDAR_TYPES, briefCalendarSchema, TRI_STATES, briefSchema;
|
|
48165
48345
|
var init_brief2 = __esm({
|
|
48166
48346
|
"src/intake-to-build/brief.ts"() {
|
|
48167
48347
|
"use strict";
|
|
48168
|
-
|
|
48348
|
+
import_zod80 = require("zod");
|
|
48169
48349
|
BRIEF_SCHEMA_VERSION = "0.1";
|
|
48170
48350
|
PRESETS = [
|
|
48171
48351
|
"generic",
|
|
@@ -48175,105 +48355,105 @@ var init_brief2 = __esm({
|
|
|
48175
48355
|
"ecom",
|
|
48176
48356
|
"agency"
|
|
48177
48357
|
];
|
|
48178
|
-
presetSchema =
|
|
48358
|
+
presetSchema = import_zod80.z.enum(PRESETS);
|
|
48179
48359
|
BRIEF_SOURCES = ["agency_os", "business_os", "intake_form", "hybrid"];
|
|
48180
|
-
briefSourceSchema =
|
|
48181
|
-
pricePointSchema =
|
|
48182
|
-
name:
|
|
48183
|
-
price:
|
|
48360
|
+
briefSourceSchema = import_zod80.z.enum(BRIEF_SOURCES);
|
|
48361
|
+
pricePointSchema = import_zod80.z.object({
|
|
48362
|
+
name: import_zod80.z.string(),
|
|
48363
|
+
price: import_zod80.z.string()
|
|
48184
48364
|
});
|
|
48185
|
-
staffMemberSchema =
|
|
48186
|
-
name:
|
|
48187
|
-
email:
|
|
48365
|
+
staffMemberSchema = import_zod80.z.object({
|
|
48366
|
+
name: import_zod80.z.string().min(1),
|
|
48367
|
+
email: import_zod80.z.string().min(1),
|
|
48188
48368
|
/** Job title as the client wrote it ("Front desk", "Provider"); omitted when
|
|
48189
48369
|
* the line had none. The plan decides admin/user from it. */
|
|
48190
|
-
role:
|
|
48191
|
-
mobile:
|
|
48370
|
+
role: import_zod80.z.string().optional(),
|
|
48371
|
+
mobile: import_zod80.z.string().optional()
|
|
48192
48372
|
});
|
|
48193
48373
|
BRIEF_CALENDAR_TYPES = ["one_on_one", "round_robin", "class"];
|
|
48194
|
-
briefCalendarSchema =
|
|
48195
|
-
name:
|
|
48196
|
-
type:
|
|
48197
|
-
staffNames:
|
|
48374
|
+
briefCalendarSchema = import_zod80.z.object({
|
|
48375
|
+
name: import_zod80.z.string().min(1),
|
|
48376
|
+
type: import_zod80.z.enum(BRIEF_CALENDAR_TYPES),
|
|
48377
|
+
staffNames: import_zod80.z.array(import_zod80.z.string()),
|
|
48198
48378
|
/** Appointment length in minutes, when the client said one ("15 minutes").
|
|
48199
48379
|
* Lands on the plan calendar as slotDuration (finding 25). */
|
|
48200
|
-
durationMinutes:
|
|
48380
|
+
durationMinutes: import_zod80.z.number().int().positive().optional()
|
|
48201
48381
|
});
|
|
48202
48382
|
TRI_STATES = ["yes", "no", "unsure"];
|
|
48203
|
-
briefSchema =
|
|
48204
|
-
schemaVersion:
|
|
48205
|
-
briefId:
|
|
48383
|
+
briefSchema = import_zod80.z.object({
|
|
48384
|
+
schemaVersion: import_zod80.z.string(),
|
|
48385
|
+
briefId: import_zod80.z.string(),
|
|
48206
48386
|
preset: presetSchema,
|
|
48207
48387
|
briefSource: briefSourceSchema,
|
|
48208
48388
|
/** Partner-OS deep structures (ICA / offer / brand-DNA), carried verbatim. */
|
|
48209
|
-
extended:
|
|
48210
|
-
business:
|
|
48211
|
-
name:
|
|
48212
|
-
type:
|
|
48213
|
-
website:
|
|
48214
|
-
location:
|
|
48215
|
-
timezone:
|
|
48389
|
+
extended: import_zod80.z.record(import_zod80.z.string(), import_zod80.z.unknown()).optional(),
|
|
48390
|
+
business: import_zod80.z.object({
|
|
48391
|
+
name: import_zod80.z.string(),
|
|
48392
|
+
type: import_zod80.z.string().optional(),
|
|
48393
|
+
website: import_zod80.z.string().optional(),
|
|
48394
|
+
location: import_zod80.z.string().optional(),
|
|
48395
|
+
timezone: import_zod80.z.string().optional(),
|
|
48216
48396
|
// Ratified additions (atlas 2026-06-15). Enum-ish but kept as strings for
|
|
48217
48397
|
// the same tolerance reason as business.type (don't reject valid briefs).
|
|
48218
|
-
teamSize:
|
|
48219
|
-
monthlyLeadVolume:
|
|
48220
|
-
hours:
|
|
48398
|
+
teamSize: import_zod80.z.string().optional(),
|
|
48399
|
+
monthlyLeadVolume: import_zod80.z.string().optional(),
|
|
48400
|
+
hours: import_zod80.z.string().optional()
|
|
48221
48401
|
}).passthrough(),
|
|
48222
|
-
offer:
|
|
48223
|
-
summary:
|
|
48402
|
+
offer: import_zod80.z.object({
|
|
48403
|
+
summary: import_zod80.z.string().optional(),
|
|
48224
48404
|
// Parsed best-effort; tolerate a raw string when parsing was not possible.
|
|
48225
|
-
pricePoints:
|
|
48226
|
-
leadMagnet:
|
|
48227
|
-
avgDealValue:
|
|
48405
|
+
pricePoints: import_zod80.z.union([import_zod80.z.array(pricePointSchema), import_zod80.z.string()]).optional(),
|
|
48406
|
+
leadMagnet: import_zod80.z.string().optional(),
|
|
48407
|
+
avgDealValue: import_zod80.z.string().optional()
|
|
48228
48408
|
}).passthrough().optional(),
|
|
48229
|
-
audience:
|
|
48230
|
-
ideal:
|
|
48231
|
-
painPoints:
|
|
48232
|
-
objections:
|
|
48409
|
+
audience: import_zod80.z.object({
|
|
48410
|
+
ideal: import_zod80.z.string().optional(),
|
|
48411
|
+
painPoints: import_zod80.z.array(import_zod80.z.string()).optional(),
|
|
48412
|
+
objections: import_zod80.z.array(import_zod80.z.string()).optional()
|
|
48233
48413
|
}).passthrough().optional(),
|
|
48234
|
-
goal:
|
|
48414
|
+
goal: import_zod80.z.object({
|
|
48235
48415
|
// Kept as string: the form option labels are the canonical values, but
|
|
48236
48416
|
// the contract sample shortens some (e.g. "high-touch"). See §7 note.
|
|
48237
|
-
primary:
|
|
48238
|
-
salesStages:
|
|
48239
|
-
bookingNeeded:
|
|
48240
|
-
followUpStyle:
|
|
48417
|
+
primary: import_zod80.z.string(),
|
|
48418
|
+
salesStages: import_zod80.z.array(import_zod80.z.string()).optional(),
|
|
48419
|
+
bookingNeeded: import_zod80.z.boolean().optional(),
|
|
48420
|
+
followUpStyle: import_zod80.z.string().optional()
|
|
48241
48421
|
}).passthrough(),
|
|
48242
|
-
channels:
|
|
48243
|
-
email:
|
|
48244
|
-
sms:
|
|
48245
|
-
a2pStatus:
|
|
48246
|
-
payment:
|
|
48247
|
-
calendarConnected:
|
|
48248
|
-
social:
|
|
48422
|
+
channels: import_zod80.z.object({
|
|
48423
|
+
email: import_zod80.z.boolean().optional(),
|
|
48424
|
+
sms: import_zod80.z.boolean().optional(),
|
|
48425
|
+
a2pStatus: import_zod80.z.string().optional(),
|
|
48426
|
+
payment: import_zod80.z.string().optional(),
|
|
48427
|
+
calendarConnected: import_zod80.z.boolean().optional(),
|
|
48428
|
+
social: import_zod80.z.array(import_zod80.z.string()).optional(),
|
|
48249
48429
|
/** "Do you already have a phone number in GoHighLevel?" */
|
|
48250
|
-
hasPhoneNumber:
|
|
48430
|
+
hasPhoneNumber: import_zod80.z.enum(TRI_STATES).optional()
|
|
48251
48431
|
}).passthrough().optional(),
|
|
48252
48432
|
/** Section G — who is on the account and who gets pinged. */
|
|
48253
|
-
team:
|
|
48254
|
-
staff:
|
|
48433
|
+
team: import_zod80.z.object({
|
|
48434
|
+
staff: import_zod80.z.array(staffMemberSchema).optional(),
|
|
48255
48435
|
/** Who is notified about new leads: a staff name, or "the owner". */
|
|
48256
|
-
notifyName:
|
|
48436
|
+
notifyName: import_zod80.z.string().optional(),
|
|
48257
48437
|
/** Who takes booking / follow-up calls: a staff name, or "the owner". */
|
|
48258
|
-
callsName:
|
|
48438
|
+
callsName: import_zod80.z.string().optional()
|
|
48259
48439
|
}).passthrough().optional(),
|
|
48260
48440
|
/** Section H — every booking calendar the client asked for. */
|
|
48261
|
-
calendars:
|
|
48441
|
+
calendars: import_zod80.z.array(briefCalendarSchema).optional(),
|
|
48262
48442
|
/** Section I — copy inputs, verbatim. */
|
|
48263
|
-
voice:
|
|
48264
|
-
threeWords:
|
|
48265
|
-
signatureLine:
|
|
48443
|
+
voice: import_zod80.z.object({
|
|
48444
|
+
threeWords: import_zod80.z.string().optional(),
|
|
48445
|
+
signatureLine: import_zod80.z.string().optional()
|
|
48266
48446
|
}).passthrough().optional(),
|
|
48267
|
-
assets:
|
|
48268
|
-
existingPipeline:
|
|
48269
|
-
existingWorkflows:
|
|
48270
|
-
brand:
|
|
48271
|
-
notes:
|
|
48447
|
+
assets: import_zod80.z.object({
|
|
48448
|
+
existingPipeline: import_zod80.z.string().optional(),
|
|
48449
|
+
existingWorkflows: import_zod80.z.string().optional(),
|
|
48450
|
+
brand: import_zod80.z.string().optional(),
|
|
48451
|
+
notes: import_zod80.z.string().optional()
|
|
48272
48452
|
}).passthrough().optional(),
|
|
48273
|
-
flags:
|
|
48453
|
+
flags: import_zod80.z.array(import_zod80.z.string()).optional(),
|
|
48274
48454
|
/** Parse-time notes from the normalizer (a staff line it could not read,
|
|
48275
48455
|
* a calendar type it had to assume). Never fatal; surfaced by validate_brief. */
|
|
48276
|
-
warnings:
|
|
48456
|
+
warnings: import_zod80.z.array(import_zod80.z.string()).optional()
|
|
48277
48457
|
}).strict();
|
|
48278
48458
|
}
|
|
48279
48459
|
});
|
|
@@ -51391,7 +51571,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
51391
51571
|
"get_intake_question_set",
|
|
51392
51572
|
`Return the Intake-to-Build question set (the questions the installed intake form asks) plus each question's GHL field mapping and the Brief field it feeds. Read-only. Pass industry (e.g. "clinic", "med-spa") to see the tailored set \u2014 base questions + that industry's pack + the agency's own overlay, exactly what install_intake_form installs for that industry; omit it for the base set. Use this to review or render the intake before installing it.`,
|
|
51393
51573
|
{
|
|
51394
|
-
industry:
|
|
51574
|
+
industry: import_zod81.z.string().optional().describe(`Industry pack slug to compose in (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set.`)
|
|
51395
51575
|
},
|
|
51396
51576
|
async ({ industry }) => {
|
|
51397
51577
|
const agency = readOverlay();
|
|
@@ -51422,32 +51602,32 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
51422
51602
|
"validate_brief",
|
|
51423
51603
|
"Validate an Intake-to-Build Brief object against the \xA74 schema. Returns {valid, errors}. Use to check a normalized brief before handing it to the plan-generation skill.",
|
|
51424
51604
|
{
|
|
51425
|
-
brief:
|
|
51605
|
+
brief: import_zod81.z.record(import_zod81.z.string(), import_zod81.z.unknown()).describe("The Brief object to validate.")
|
|
51426
51606
|
},
|
|
51427
51607
|
async ({ brief }) => validateBrief(brief)
|
|
51428
51608
|
);
|
|
51429
51609
|
const buildPlanSchema2 = {
|
|
51430
|
-
plan:
|
|
51431
|
-
fromPreset:
|
|
51432
|
-
brief:
|
|
51433
|
-
preset:
|
|
51434
|
-
copy:
|
|
51435
|
-
|
|
51436
|
-
ref:
|
|
51437
|
-
subject:
|
|
51438
|
-
html:
|
|
51439
|
-
body:
|
|
51610
|
+
plan: import_zod81.z.record(import_zod81.z.string(), import_zod81.z.unknown()).optional().describe("The approved \xA75 Build Plan object. Omit it and pass useSavedPlan:true to build from the plan already saved for this account."),
|
|
51611
|
+
fromPreset: import_zod81.z.boolean().optional().describe("PLAN-FROM-PRESET (PRD row 4d): compose the \xA75 plan SERVER-SIDE from the industry preset + the \xA74 brief, deterministically, in milliseconds \u2014 never author workflow actions by hand when a preset covers the brief. Pass `brief` (required) and optionally `preset` and `copy`; omit `plan`. The composed plan carries the preset's full structure (workflows, templates with complete send-ready copy token-filled from the brief, staff users, calendars with the brief's appointment lengths, pipeline stages, notification targets) and then flows through the exact same validate \u2192 dry_run/execute path as a passed plan, including being saved on execute. The dry_run response lists copySlots \u2014 the template refs whose subject/body the model MAY refine via `copy` on the execute call. If the preset cannot cover the brief the tool answers phase:\"compose\" with the reasons; only then author a plan by hand."),
|
|
51612
|
+
brief: import_zod81.z.record(import_zod81.z.string(), import_zod81.z.unknown()).optional().describe("fromPreset only: the \xA74 Brief (from normalize_submission_to_brief, or assembled from the cockpit intake answers)."),
|
|
51613
|
+
preset: import_zod81.z.string().optional().describe("fromPreset only: which preset to compose from \u2014 a preset id (med_spa, clinic, coach, local_service, ecom, generic), an alias (dental, medspa, home_services\u2026), or an industry slug (med-spa, local-service, ecommerce\u2026). Defaults to the brief's own preset field; an unknown value falls back to the generic preset (noted in the response)."),
|
|
51614
|
+
copy: import_zod81.z.array(
|
|
51615
|
+
import_zod81.z.object({
|
|
51616
|
+
ref: import_zod81.z.string().describe("email_template.* or sms_template.* ref from the composed plan"),
|
|
51617
|
+
subject: import_zod81.z.string().optional(),
|
|
51618
|
+
html: import_zod81.z.string().optional(),
|
|
51619
|
+
body: import_zod81.z.string().optional()
|
|
51440
51620
|
})
|
|
51441
51621
|
).optional().describe("fromPreset only: client-specific copy overrides \u2014 subject/html for an email template, body for an SMS template. Copy ONLY; structure is never overridable. Overrides land on the template AND its reviewable asset. An unknown ref is an error, never a silent no-op."),
|
|
51442
|
-
useSavedPlan:
|
|
51443
|
-
replaceSavedPlan:
|
|
51444
|
-
mode:
|
|
51445
|
-
locationId:
|
|
51446
|
-
metHandoffs:
|
|
51447
|
-
publishWorkflows:
|
|
51448
|
-
onConflict:
|
|
51622
|
+
useSavedPlan: import_zod81.z.boolean().optional().describe("Load the approved plan saved for the active location by an earlier execute (plans/<locationId>.json on this machine) instead of passing one. The right choice for ANY re-run: re-authoring a plan drifts and duplicates objects."),
|
|
51623
|
+
replaceSavedPlan: import_zod81.z.boolean().optional().describe("Only with a new `plan` whose planId differs from the saved one: confirms the operator approved the new plan, so it replaces the saved plan on execute. Without it, a differently named plan is refused while a saved plan exists."),
|
|
51624
|
+
mode: import_zod81.z.enum(["dry_run", "execute"]).optional().describe("dry_run (default) = resolve/expand/scan/report, no writes. execute = live writes. Run dry_run FIRST, show the operator the report, and run execute only on their go."),
|
|
51625
|
+
locationId: import_zod81.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first)."),
|
|
51626
|
+
metHandoffs: import_zod81.z.array(import_zod81.z.string()).optional().describe('Handoff refs the operator has already satisfied (e.g. ["handoff.a2p"]) \u2014 lifts their gate so dependent workflows are not held DRAFT.'),
|
|
51627
|
+
publishWorkflows: import_zod81.z.boolean().optional().describe("If true, ungated workflows would be published instead of left DRAFT. Default false (DRAFT)."),
|
|
51628
|
+
onConflict: import_zod81.z.enum(["skip", "abort"]).optional().describe("skip (default) = bind same-named existing objects and continue. abort = report conflicts as a halt.")
|
|
51449
51629
|
};
|
|
51450
|
-
const buildPlanArgs =
|
|
51630
|
+
const buildPlanArgs = import_zod81.z.object(buildPlanSchema2);
|
|
51451
51631
|
const runBuildPlan = async ({ plan: planArgIn, fromPreset, brief: briefArg, preset: presetArg, copy: copyArg, useSavedPlan, replaceSavedPlan, mode, locationId: locationId2, metHandoffs, publishWorkflows, onConflict }, persistPreview = false) => {
|
|
51452
51632
|
try {
|
|
51453
51633
|
const resolvedMode = mode ?? "dry_run";
|
|
@@ -51765,7 +51945,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
51765
51945
|
server2.tool(
|
|
51766
51946
|
"validate_build_plan",
|
|
51767
51947
|
"Validate a Build Plan and its references. With preview:true, compose from a brief or resolve a plan using the same executable planner as Build, read existing account assets, and save the complete preview locally. This tool cannot execute or write to the account. Approve the saved plan before running Build.",
|
|
51768
|
-
{ ...buildPlanSchema2, mode:
|
|
51948
|
+
{ ...buildPlanSchema2, mode: import_zod81.z.literal("dry_run").optional(), preview: import_zod81.z.boolean().optional().describe("Prepare and save the executable preview using account reads only.") },
|
|
51769
51949
|
async ({ preview, ...args }) => {
|
|
51770
51950
|
try {
|
|
51771
51951
|
return preview ? await runBuildPlan({ ...args, mode: "dry_run", publishWorkflows: false }, true) : jsonResponse(validateBuildPlan(args.plan));
|
|
@@ -51778,9 +51958,9 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
51778
51958
|
"revert_build",
|
|
51779
51959
|
`Undo a Blueprint build: remove the objects a recorded apply_build_plan run CREATED in the current sub-account, and nothing else. PREVIEWS BY DEFAULT \u2014 with no confirm it writes nothing and returns a plain-English list of exactly what would be removed, grouped, plus what it will leave alone and why. Pass confirm:"DELETE" to actually remove them. Acts on one recorded run: pass runId, or omit it for the most recent run in the ACTIVE account (runs from any other sub-account are in a different journal and can never be reached from here). The one hard rule: it removes only objects the run's never-clobber ledger recorded as CREATED \u2014 anything the build merely BOUND to (a pipeline, tag, calendar or workflow that was already in the account) is never touched, so a build run against an account that already had assets cannot delete the client's own work. Deletes run in the exact reverse of the build order (workflows first, then templates, funnels and their pages, forms, calendars, custom values, tags, custom fields, pipelines) so nothing is removed while something else still points at it. A refusal from GoHighLevel never aborts the run: refusals are collected and reported at the end with what to do about each. After the deletes the account is re-read independently and each object is reported as confirmed gone or as accepted-but-unconfirmed. Idempotent: run it twice and the second run finds nothing left and says so. Two kinds are deliberately NEVER removed automatically \u2014 staff users (a person's login; removing it can strip them off appointments and assignments) and text-message templates (GoHighLevel exposes no delete for them) \u2014 both are listed with where to remove them by hand.`,
|
|
51780
51960
|
{
|
|
51781
|
-
runId:
|
|
51782
|
-
confirm:
|
|
51783
|
-
locationId:
|
|
51961
|
+
runId: import_zod81.z.string().optional().describe('The recorded build run to undo (the `runId` apply_build_plan returned, e.g. "r-20260827T0142-9c1e"). Omit for the most recent run recorded for the active sub-account.'),
|
|
51962
|
+
confirm: import_zod81.z.literal("DELETE").optional().describe('Omit for a preview (no writes at all). Pass "DELETE" to actually remove the objects the preview listed.'),
|
|
51963
|
+
locationId: import_zod81.z.string().optional().describe("Target sub-account. MUST match the active location; if it differs the tool refuses (confirm/switch first).")
|
|
51784
51964
|
},
|
|
51785
51965
|
async ({ runId, confirm: confirm2, locationId: locationId2 }) => {
|
|
51786
51966
|
try {
|
|
@@ -51958,10 +52138,10 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
51958
52138
|
"install_intake_form",
|
|
51959
52139
|
`Install the Intake-to-Build client-intake form into the CURRENT GHL location (whatever get_current_location returns). Account-agnostic, zero hardcoded IDs. Creates any missing intake custom fields (idempotent \u2014 reused on re-run), then builds the form with the proven GHL form-builder field shapes and verifies it. Pass industry (e.g. "clinic", "med-spa") to install the TAILORED set \u2014 base questions + that industry's pack + the agency's own overlay (added / removed questions), the same set the intake interview asks \u2014 so no cockpit answer is left without a form field; omit it for the base set. Returns {formId, formUrl, fieldMap} \u2014 formUrl is the public link to send the client; keep fieldMap. NOTE: the answers come back on a CONTACT tagged "blueprint-intake", not as a form submission (GoHighLevel reports no submissions for forms created through the API), so read the contact and assemble the Brief from its fields. Pass dryRun:true to preview what would be created without writing. Pass formId to update an existing intake form in place (e.g. to add an industry's questions to a form installed without one) instead of creating a new one.`,
|
|
51960
52140
|
{
|
|
51961
|
-
dryRun:
|
|
51962
|
-
formId:
|
|
51963
|
-
formName:
|
|
51964
|
-
industry:
|
|
52141
|
+
dryRun: import_zod81.z.boolean().optional().describe("Preview the fields/form that would be created without writing anything."),
|
|
52142
|
+
formId: import_zod81.z.string().optional().describe("Update this existing form in place instead of creating a new one."),
|
|
52143
|
+
formName: import_zod81.z.string().optional().describe(`Form name. Defaults to "${INTAKE_FORM_NAME}".`),
|
|
52144
|
+
industry: import_zod81.z.string().optional().describe(`Industry pack to install with the base set (${INDUSTRY_PACKS.map((p) => p.slug).join(", ")}). Omit for the base set only.`)
|
|
51965
52145
|
},
|
|
51966
52146
|
async ({ dryRun, formId, formName, industry }) => {
|
|
51967
52147
|
try {
|
|
@@ -52080,10 +52260,10 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
52080
52260
|
"normalize_submission_to_brief",
|
|
52081
52261
|
`Read an intake form submission and normalize it into a \xA74 Brief (briefSource:"intake_form"). ONLY works for a form whose submissions GoHighLevel actually reports, which in practice means a form built in GHL's own editor \u2014 for an intake form installed by install_intake_form it will answer "No submissions found" even after the client has filled it in, and the answers should be read from the contact tagged "blueprint-intake" instead. Pass the formId; by default the most recent submission is used (or pass submissionId). The intakeKey->customFieldId map is taken from fieldMap if provided (the install_intake_form output, most robust), otherwise reconstructed from the live form. Returns {brief, validation, submissionId} \u2014 validation flags any missing required fields (e.g. an incomplete submission).`,
|
|
52082
52262
|
{
|
|
52083
|
-
formId:
|
|
52084
|
-
submissionId:
|
|
52085
|
-
fieldMap:
|
|
52086
|
-
preset:
|
|
52263
|
+
formId: import_zod81.z.string().describe("The intake form ID (from install_intake_form)."),
|
|
52264
|
+
submissionId: import_zod81.z.string().optional().describe("Specific submission to normalize. Defaults to the most recent."),
|
|
52265
|
+
fieldMap: import_zod81.z.record(import_zod81.z.string(), import_zod81.z.string()).optional().describe("intakeKey -> customFieldId map from install_intake_form. Reconstructed from the form if omitted."),
|
|
52266
|
+
preset: import_zod81.z.string().optional().describe("Override the preset. Defaults to one derived from business_type.")
|
|
52087
52267
|
},
|
|
52088
52268
|
async ({ formId, submissionId, fieldMap, preset }) => {
|
|
52089
52269
|
try {
|
|
@@ -52112,7 +52292,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
52112
52292
|
const formFull = await formApiRequest(bc, "GET", `/${formId}?locationId=${locationId2}`);
|
|
52113
52293
|
resolvedMap = buildFieldMapFromFormFields(extractFormFields(formFull));
|
|
52114
52294
|
}
|
|
52115
|
-
const presetSchema2 =
|
|
52295
|
+
const presetSchema2 = import_zod81.z.enum(["generic", "med_spa", "clinic_launch_a2p", "coach", "ecom", "agency"]).optional();
|
|
52116
52296
|
const presetParsed = presetSchema2.safeParse(preset);
|
|
52117
52297
|
const brief = normalizeSubmissionToBrief({
|
|
52118
52298
|
others,
|
|
@@ -52138,7 +52318,7 @@ function intakeFormMatches(expected, actual) {
|
|
|
52138
52318
|
const keys = ["id", "tag", "type", "hiddenFieldQueryKey", "required", "label", "options", "picklistOptions"];
|
|
52139
52319
|
return expected.every((field, i) => keys.every((key) => field[key] === void 0 || JSON.stringify(field[key]) === JSON.stringify(actual[i]?.[key])));
|
|
52140
52320
|
}
|
|
52141
|
-
var
|
|
52321
|
+
var import_zod81, customFieldItemSchema, sleep6;
|
|
52142
52322
|
var init_intake_to_build = __esm({
|
|
52143
52323
|
"src/tools/intake-to-build.ts"() {
|
|
52144
52324
|
"use strict";
|
|
@@ -52146,7 +52326,7 @@ var init_intake_to_build = __esm({
|
|
|
52146
52326
|
init_workflow_repair_approval();
|
|
52147
52327
|
init_build_contract();
|
|
52148
52328
|
init_build_obligations();
|
|
52149
|
-
|
|
52329
|
+
import_zod81 = require("zod");
|
|
52150
52330
|
init_workflow_builder_client();
|
|
52151
52331
|
init_tool_helpers();
|
|
52152
52332
|
init_form_builder();
|
|
@@ -52170,16 +52350,16 @@ var init_intake_to_build = __esm({
|
|
|
52170
52350
|
init_plan_from_preset();
|
|
52171
52351
|
init_executor();
|
|
52172
52352
|
init_execute();
|
|
52173
|
-
customFieldItemSchema =
|
|
52174
|
-
id:
|
|
52175
|
-
name:
|
|
52176
|
-
fieldKey:
|
|
52177
|
-
dataType:
|
|
52178
|
-
model:
|
|
52179
|
-
parentId:
|
|
52180
|
-
position:
|
|
52181
|
-
dateAdded:
|
|
52182
|
-
picklistOptions:
|
|
52353
|
+
customFieldItemSchema = import_zod81.z.object({
|
|
52354
|
+
id: import_zod81.z.string(),
|
|
52355
|
+
name: import_zod81.z.string(),
|
|
52356
|
+
fieldKey: import_zod81.z.string(),
|
|
52357
|
+
dataType: import_zod81.z.string(),
|
|
52358
|
+
model: import_zod81.z.string().optional(),
|
|
52359
|
+
parentId: import_zod81.z.string().optional(),
|
|
52360
|
+
position: import_zod81.z.number().optional(),
|
|
52361
|
+
dateAdded: import_zod81.z.string().optional(),
|
|
52362
|
+
picklistOptions: import_zod81.z.array(import_zod81.z.string()).optional()
|
|
52183
52363
|
}).passthrough();
|
|
52184
52364
|
sleep6 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
52185
52365
|
}
|
|
@@ -52564,28 +52744,34 @@ async function runDesignCli(argv, io = { out: (s) => process.stdout.write(s + "\
|
|
|
52564
52744
|
const outDir = str3("out") ?? (0, import_node_path8.join)((0, import_node_os2.tmpdir)(), `design-run-${brand}-${medium}-${Date.now()}`);
|
|
52565
52745
|
const model = str3("model") ?? DEFAULT_MODEL;
|
|
52566
52746
|
io.out(`design-run: ${medium} for ${brand} on your Claude (${model}), up to ${passes} pass(es) \u2192 ${outDir}`);
|
|
52567
|
-
|
|
52568
|
-
|
|
52569
|
-
|
|
52570
|
-
|
|
52571
|
-
|
|
52572
|
-
|
|
52573
|
-
|
|
52574
|
-
|
|
52575
|
-
|
|
52576
|
-
|
|
52577
|
-
|
|
52578
|
-
|
|
52579
|
-
|
|
52580
|
-
|
|
52581
|
-
|
|
52582
|
-
|
|
52583
|
-
|
|
52584
|
-
|
|
52585
|
-
|
|
52586
|
-
|
|
52587
|
-
|
|
52588
|
-
|
|
52747
|
+
let r;
|
|
52748
|
+
try {
|
|
52749
|
+
r = await runDesign({
|
|
52750
|
+
brand,
|
|
52751
|
+
brief: {
|
|
52752
|
+
medium,
|
|
52753
|
+
content: { headline: headline3, points: points.split("|").map((s) => s.trim()).filter(Boolean), audience: str3("audience"), job: str3("job"), ctaLabel: str3("cta"), proof: str3("proof") },
|
|
52754
|
+
options: {
|
|
52755
|
+
...str3("mode") && { mode: str3("mode") },
|
|
52756
|
+
...a.slides && { slides: Number(a.slides) },
|
|
52757
|
+
theme: str3("theme") ?? "both",
|
|
52758
|
+
...conversion ? { pageMode: "conversion" } : {}
|
|
52759
|
+
},
|
|
52760
|
+
...conversion ? { conversion } : {}
|
|
52761
|
+
},
|
|
52762
|
+
...gate2 ? { gate: gate2 } : {},
|
|
52763
|
+
outDir,
|
|
52764
|
+
model,
|
|
52765
|
+
maxPasses: passes,
|
|
52766
|
+
headless: !a.headed,
|
|
52767
|
+
onPass: (p) => io.out(` pass ${p.pass}: ${p.lint ? p.verdict === "PASS" ? "PASS" : `${p.verdict}${p.failed.length ? " \u2014 " + p.failed.join(" | ") : ""}` : `contract: ${p.lintProblems.join("; ")}`} ($${p.costUsd.toFixed(3)}, ${Math.round(p.ms / 1e3)}s)`),
|
|
52768
|
+
// Two verdicts, never one: craft is measured, copy is judged.
|
|
52769
|
+
...str3("grade-copy") ? { gradeCopyFor: str3("grade-copy") } : {}
|
|
52770
|
+
});
|
|
52771
|
+
} catch (error) {
|
|
52772
|
+
io.err(error instanceof import_zod83.z.ZodError ? designValidationMessage(error) : error instanceof Error ? error.message : "The design run could not finish. Check the design details before trying again.");
|
|
52773
|
+
return 2;
|
|
52774
|
+
}
|
|
52589
52775
|
io.out(summariseRun(r));
|
|
52590
52776
|
if (r.file) io.out(`file: ${r.file}`);
|
|
52591
52777
|
if (r.report) io.out(`report: ${r.report}`);
|
|
@@ -52595,10 +52781,12 @@ async function runDesignCli(argv, io = { out: (s) => process.stdout.write(s + "\
|
|
|
52595
52781
|
}
|
|
52596
52782
|
return r.ok ? 0 : 1;
|
|
52597
52783
|
}
|
|
52598
|
-
var import_node_child_process6, import_node_path8, import_node_os2, USAGE2;
|
|
52784
|
+
var import_zod83, import_node_child_process6, import_node_path8, import_node_os2, USAGE2;
|
|
52599
52785
|
var init_cli2 = __esm({
|
|
52600
52786
|
"src/command-os/design/cli.ts"() {
|
|
52601
52787
|
"use strict";
|
|
52788
|
+
import_zod83 = require("zod");
|
|
52789
|
+
init_brief_validation();
|
|
52602
52790
|
import_node_child_process6 = require("node:child_process");
|
|
52603
52791
|
import_node_path8 = require("node:path");
|
|
52604
52792
|
import_node_os2 = require("node:os");
|
|
@@ -52695,7 +52883,7 @@ init_credentials_store();
|
|
|
52695
52883
|
init_setup_tool();
|
|
52696
52884
|
|
|
52697
52885
|
// src/tools/skills.ts
|
|
52698
|
-
var
|
|
52886
|
+
var import_zod82 = require("zod");
|
|
52699
52887
|
|
|
52700
52888
|
// src/skill-installer.ts
|
|
52701
52889
|
var import_node_crypto12 = require("node:crypto");
|
|
@@ -52831,7 +53019,7 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
|
|
|
52831
53019
|
"install_skills",
|
|
52832
53020
|
"Install (or repair) the guided skills bundled with GHL Command \u2014 Blueprint (build a whole client account from one intake), Clone Site (clone and rebrand a live web page for a client, with a rights declaration and a pre-launch liability report), and GHL Reports (verified counts, lists, and weekly reports without token burn) \u2014 into your ~/.claude/skills/ so Claude can use them. Runs automatically on startup; call this to verify what is installed, or to re-install after deleting a skill. NEVER overwrites files you have edited (your version is kept and reported). Restart Claude after install for new skills to load.",
|
|
52833
53021
|
{
|
|
52834
|
-
targetDir:
|
|
53022
|
+
targetDir: import_zod82.z.string().optional().describe("Override the install directory. Default: ~/.claude/skills")
|
|
52835
53023
|
},
|
|
52836
53024
|
async ({ targetDir }) => {
|
|
52837
53025
|
try {
|