@elitedcs/ghl-mcp 3.90.0 → 3.90.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/dist/index.js +349 -81
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.90.1: Add them for me and Confirm and build add exactly the people you were shown
|
|
4
|
+
|
|
5
|
+
This release is everything in 3.90.0, plus the fix below. It fixes behavior that shipped in 3.89.0 and 3.90.0.
|
|
6
|
+
|
|
7
|
+
**Add them for me and Confirm and build now add exactly the people shown, mark anyone getting an admin login, and refuse if the team changed.** Step 7, Staff, lists each person with their job, marks anyone whose login will be an admin with "Admin login", and says where the list came from. Confirm and build names each login it will make, the same way, before anything is made. When you press, the cockpit reads the team again first. If it changed since you looked, nobody is added and the list is shown as it is now.
|
|
8
|
+
|
|
9
|
+
**One team, the same everywhere.** The team is Your team in step 5 when it has anyone on it, otherwise the client's intake. Your own intake draft counts only when the client has not submitted the intake. In 3.89.0 and 3.90.0 an older draft could add someone who had left, with an admin login, and Build could make logins from the intake while step 7 checked Your team.
|
|
10
|
+
|
|
11
|
+
**Someone already in the account keeps their login as it is.** If the list would give them a different level, step 7 says so, and you change it in GoHighLevel.
|
|
12
|
+
|
|
13
|
+
**After Add them for me, step 7 says what happened:** who was added, who already had a login, and anyone who got a login but is no longer on the team, so you can remove it in GoHighLevel.
|
|
14
|
+
|
|
15
|
+
**A new login is never made twice while GoHighLevel catches up.** GoHighLevel can take a minute to list a login it has just made. Until it does, step 7 and the board say the person "is still being set up in GoHighLevel", and Add them for me does not send them again, from any page or window, for up to 10 minutes. If GoHighLevel insisted on a password, step 7 now shows the one-time password, as the board does.
|
|
16
|
+
|
|
17
|
+
**Known limits, fixed in the next release.** The board's staff panel does not show when someone's login level differs from the list. If someone is taken off the team while their login is being made and the connection drops, step 7 can still say they could not be added. Without an agency key, step 7 says your plan does not let you add people. The staff step says Intake waits for it (only Build does), and says calendars and lead alerts will use the people found, which it does not check. Blueprint Review says staff are not in the account when it could not read them. When nobody's job says owner, Build makes the first person listed an admin, while Add them for me makes them a user; Build's confirmation shows it before anything is made.
|
|
18
|
+
|
|
19
|
+
After Add them for me or Confirm and build, check the client's GoHighLevel Users list.
|
|
20
|
+
|
|
3
21
|
## 3.90.0: the cockpit never acts on the wrong client, and refuses a change made from an old page
|
|
4
22
|
|
|
5
23
|
This release is everything in 3.89.0, plus the changes below.
|
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -8255,10 +8255,16 @@ function pickUsers(raw) {
|
|
|
8255
8255
|
const last = typeof o.lastName === "string" ? o.lastName : "";
|
|
8256
8256
|
const name = typeof o.name === "string" && o.name ? o.name : `${first2} ${last}`.trim();
|
|
8257
8257
|
const email = typeof o.email === "string" ? o.email : void 0;
|
|
8258
|
-
|
|
8258
|
+
const roles = o.roles && typeof o.roles === "object" ? o.roles : void 0;
|
|
8259
|
+
const role = roles && typeof roles.role === "string" ? roles.role : void 0;
|
|
8260
|
+
out.push({ id, name: name || id, email, ...role ? { role } : {} });
|
|
8259
8261
|
}
|
|
8260
8262
|
return out;
|
|
8261
8263
|
}
|
|
8264
|
+
function isDefinitiveRefusal(error) {
|
|
8265
|
+
const m = /^GHL API Error (\d{3})\b/.exec(error);
|
|
8266
|
+
return Boolean(m && m[1].startsWith("4") && m[1] !== "408");
|
|
8267
|
+
}
|
|
8262
8268
|
function classifyCreateUserError(error) {
|
|
8263
8269
|
return error.toLowerCase().includes(FEATURE_UNAVAILABLE_TEXT) ? "feature_unavailable" : "other";
|
|
8264
8270
|
}
|
|
@@ -8301,6 +8307,7 @@ async function createOrBindUserWith(args, deps) {
|
|
|
8301
8307
|
lastError = msg(e);
|
|
8302
8308
|
if (inBuildRequestScope() && isRetryableAccountError(e)) break;
|
|
8303
8309
|
if (classifyCreateUserError(lastError) === "feature_unavailable") break;
|
|
8310
|
+
if (!isDefinitiveRefusal(lastError)) break;
|
|
8304
8311
|
if (/password/i.test(lastError) && !password) {
|
|
8305
8312
|
password = generateTemporaryPassword();
|
|
8306
8313
|
generatedPassword = password;
|
|
@@ -36471,7 +36478,7 @@ var require_package = __commonJS({
|
|
|
36471
36478
|
"package.json"(exports2, module2) {
|
|
36472
36479
|
module2.exports = {
|
|
36473
36480
|
name: "@elitedcs/ghl-mcp",
|
|
36474
|
-
version: "3.90.
|
|
36481
|
+
version: "3.90.1",
|
|
36475
36482
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
36476
36483
|
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.",
|
|
36477
36484
|
main: "dist/index.js",
|
|
@@ -44293,6 +44300,64 @@ var init_normalizer = __esm({
|
|
|
44293
44300
|
});
|
|
44294
44301
|
|
|
44295
44302
|
// src/staff-first.ts
|
|
44303
|
+
function staffWaitingLine(name) {
|
|
44304
|
+
return `${name} is still being set up in GoHighLevel. Add them for me will not add them again for up to 10 minutes.`;
|
|
44305
|
+
}
|
|
44306
|
+
function createMadeNothing(outcome) {
|
|
44307
|
+
if (outcome.ok) return false;
|
|
44308
|
+
if (outcome.stage === "verify") return false;
|
|
44309
|
+
if (outcome.stage !== "create") return true;
|
|
44310
|
+
return isDefinitiveRefusal(outcome.error ?? "");
|
|
44311
|
+
}
|
|
44312
|
+
function pendingSeen(pending, check) {
|
|
44313
|
+
if (check.status === "unreadable") return { ...pending ?? {} };
|
|
44314
|
+
const listed = new Set(check.matched.map((m) => normEmail(m.email)));
|
|
44315
|
+
return Object.fromEntries(Object.entries(pending ?? {}).filter(([email]) => !listed.has(normEmail(email))));
|
|
44316
|
+
}
|
|
44317
|
+
function settleResults(results, check) {
|
|
44318
|
+
const unreadable = check.status === "unreadable";
|
|
44319
|
+
const listed = new Set(check.matched.map((m) => normEmail(m.email)));
|
|
44320
|
+
const onTeam = new Set(check.listed.map((m) => normEmail(m.email)));
|
|
44321
|
+
for (const r of results) {
|
|
44322
|
+
const e = normEmail(r.email);
|
|
44323
|
+
if (!unreadable && !onTeam.has(e)) continue;
|
|
44324
|
+
if (unreadable && r.status === "existing") continue;
|
|
44325
|
+
if (!unreadable && listed.has(e)) {
|
|
44326
|
+
r.ok = true;
|
|
44327
|
+
if (r.status !== "existing") r.status = "created";
|
|
44328
|
+
r.said = r.status === "existing" ? `${r.name} already had a login. Nothing was created.` : `${r.name} was added. GoHighLevel should email them an invitation.`;
|
|
44329
|
+
} else if (r.refused) {
|
|
44330
|
+
r.ok = false;
|
|
44331
|
+
delete r.status;
|
|
44332
|
+
r.said = r.refusedSaid ?? r.said;
|
|
44333
|
+
} else {
|
|
44334
|
+
r.ok = false;
|
|
44335
|
+
r.said = staffWaitingLine(r.name);
|
|
44336
|
+
}
|
|
44337
|
+
}
|
|
44338
|
+
return results;
|
|
44339
|
+
}
|
|
44340
|
+
function livePending(pending, nowMs) {
|
|
44341
|
+
const out = {};
|
|
44342
|
+
for (const [email, v] of Object.entries(pending ?? {})) {
|
|
44343
|
+
const at = Date.parse(v.at);
|
|
44344
|
+
if (Number.isFinite(at) && nowMs - at >= 0 && nowMs - at < STAFF_PENDING_MS) out[normEmail(email)] = v;
|
|
44345
|
+
}
|
|
44346
|
+
return out;
|
|
44347
|
+
}
|
|
44348
|
+
function applyStaffPending(check, pending, nowMs) {
|
|
44349
|
+
const held = livePending(pending, nowMs);
|
|
44350
|
+
const waiting = check.missing.filter((m) => held[normEmail(m.email)]);
|
|
44351
|
+
if (!waiting.length) return check;
|
|
44352
|
+
const isHeld = (email) => Boolean(held[normEmail(email)]);
|
|
44353
|
+
const lines = new Map(waiting.map((m) => [normEmail(m.email), staffWaitingLine(m.name)]));
|
|
44354
|
+
return {
|
|
44355
|
+
...check,
|
|
44356
|
+
creations: (check.creations ?? []).filter((c) => !isHeld(c.email)),
|
|
44357
|
+
steps: check.missing.map((m, i) => lines.get(normEmail(m.email)) ?? check.steps[i]).filter((x) => typeof x === "string"),
|
|
44358
|
+
waiting: waiting.map((m) => ({ name: m.name, email: m.email, line: lines.get(normEmail(m.email)) }))
|
|
44359
|
+
};
|
|
44360
|
+
}
|
|
44296
44361
|
function personLabel(p) {
|
|
44297
44362
|
return `${p.name} (${p.email})`;
|
|
44298
44363
|
}
|
|
@@ -44332,22 +44397,11 @@ function intakeAnswer(answers, key2, label2) {
|
|
|
44332
44397
|
if (Array.isArray(v)) return v.map(String).join("\n");
|
|
44333
44398
|
return void 0;
|
|
44334
44399
|
}
|
|
44335
|
-
function
|
|
44336
|
-
|
|
44337
|
-
if (!roster) return out;
|
|
44338
|
-
const pairs = [
|
|
44339
|
-
[STAFF_INTAKE_KEYS.staff, roster.lines],
|
|
44340
|
-
[STAFF_INTAKE_KEYS.notify, roster.notifyName],
|
|
44341
|
-
[STAFF_INTAKE_KEYS.calls, roster.callsName]
|
|
44342
|
-
];
|
|
44343
|
-
for (const [key2, value] of pairs) {
|
|
44344
|
-
const label2 = questions.find((q3) => q3.key === key2)?.label;
|
|
44345
|
-
if (label2 && value && value.trim() && !String(out[label2] ?? "").trim()) out[label2] = value;
|
|
44346
|
-
}
|
|
44347
|
-
return out;
|
|
44400
|
+
function teamBoxDecides(roster) {
|
|
44401
|
+
return Boolean(roster && String(roster.lines ?? "").trim());
|
|
44348
44402
|
}
|
|
44349
44403
|
function teamAnswersFromRoster(answers, roster, questions) {
|
|
44350
|
-
if (!roster || !
|
|
44404
|
+
if (!roster || !teamBoxDecides(roster)) return { ...answers };
|
|
44351
44405
|
const out = { ...answers };
|
|
44352
44406
|
for (const [key2, value] of [[STAFF_INTAKE_KEYS.staff, roster.lines], [STAFF_INTAKE_KEYS.notify, roster.notifyName], [STAFF_INTAKE_KEYS.calls, roster.callsName]]) {
|
|
44353
44407
|
const label2 = questions.find((q3) => q3.key === key2)?.label;
|
|
@@ -44385,11 +44439,20 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
|
|
|
44385
44439
|
}
|
|
44386
44440
|
const matched = [];
|
|
44387
44441
|
const missing = [];
|
|
44442
|
+
const levelDiffs = [];
|
|
44388
44443
|
for (const p of listed.people) {
|
|
44389
44444
|
const u = findUserByEmail(users, p.email);
|
|
44390
|
-
if (u)
|
|
44391
|
-
|
|
44445
|
+
if (u) {
|
|
44446
|
+
matched.push({ ...p, userId: u.id });
|
|
44447
|
+
const listedRole = personToCreateInput(p).role;
|
|
44448
|
+
if (u.role && u.role !== listedRole) {
|
|
44449
|
+
const d = { name: p.name, email: p.email, ...p.role ? { job: p.role } : {}, listed: listedRole, account: u.role };
|
|
44450
|
+
levelDiffs.push({ ...d, line: staffLevelDiffLine(d) });
|
|
44451
|
+
}
|
|
44452
|
+
} else missing.push(p);
|
|
44392
44453
|
}
|
|
44454
|
+
if (levelDiffs.length) Object.assign(base, { levelDiffs });
|
|
44455
|
+
Object.assign(base, { creations: creationsFor(missing) });
|
|
44393
44456
|
if (missing.length === 0) {
|
|
44394
44457
|
return {
|
|
44395
44458
|
...base,
|
|
@@ -44451,17 +44514,23 @@ function personToCreateInput(p) {
|
|
|
44451
44514
|
...p.mobile ? { phone: p.mobile } : {}
|
|
44452
44515
|
};
|
|
44453
44516
|
}
|
|
44454
|
-
async function addMissingStaff(deps, listed, now) {
|
|
44455
|
-
const before = await readStaffCheck(deps, listed, now);
|
|
44517
|
+
async function addMissingStaff(deps, listed, now, compared) {
|
|
44518
|
+
const before = compared ?? await readStaffCheck(deps, listed, now);
|
|
44456
44519
|
if (before.status !== "needs_staff") return { results: [], check: before };
|
|
44457
44520
|
if (!deps.canCreateUsers()) {
|
|
44458
44521
|
return { results: [], check: before };
|
|
44459
44522
|
}
|
|
44460
44523
|
const results = [];
|
|
44461
44524
|
let refused = false;
|
|
44525
|
+
const waiting = new Set((before.waiting ?? []).map((w) => normEmail(w.email)));
|
|
44462
44526
|
for (const p of before.missing) {
|
|
44527
|
+
if (waiting.has(normEmail(p.email))) {
|
|
44528
|
+
results.push({ name: p.name, email: p.email, ok: false, said: staffWaitingLine(p.name) });
|
|
44529
|
+
continue;
|
|
44530
|
+
}
|
|
44463
44531
|
if (refused) {
|
|
44464
|
-
|
|
44532
|
+
const said = `${p.name} has to be added in GoHighLevel itself, for the same reason.`;
|
|
44533
|
+
results.push({ name: p.name, email: p.email, ok: false, refused: true, refusedSaid: said, said });
|
|
44465
44534
|
continue;
|
|
44466
44535
|
}
|
|
44467
44536
|
let outcome;
|
|
@@ -44475,36 +44544,86 @@ async function addMissingStaff(deps, listed, now) {
|
|
|
44475
44544
|
name: p.name,
|
|
44476
44545
|
email: p.email,
|
|
44477
44546
|
ok: true,
|
|
44478
|
-
|
|
44547
|
+
status: outcome.status,
|
|
44548
|
+
said: outcome.status === "existing" ? `${p.name} already had a login. Nothing was created.` : `${p.name} was added. GoHighLevel should email them an invitation.`,
|
|
44479
44549
|
...outcome.generatedPassword ? { generatedPassword: outcome.generatedPassword } : {}
|
|
44480
44550
|
});
|
|
44481
44551
|
} else {
|
|
44482
44552
|
if (outcome.errorKind === "feature_unavailable") refused = true;
|
|
44553
|
+
const said = outcome.errorKind === "feature_unavailable" ? `GoHighLevel does not let this agency add people from outside GoHighLevel, so ${p.name} has to be added in GoHighLevel itself.` : `${p.name} could not be added. GoHighLevel said: ${outcome.error}`;
|
|
44483
44554
|
results.push({
|
|
44484
44555
|
name: p.name,
|
|
44485
44556
|
email: p.email,
|
|
44486
44557
|
ok: false,
|
|
44487
|
-
|
|
44558
|
+
...createMadeNothing(outcome) ? { refused: true, refusedSaid: said } : {},
|
|
44559
|
+
said
|
|
44488
44560
|
});
|
|
44489
44561
|
}
|
|
44490
44562
|
}
|
|
44491
44563
|
const after = await readStaffCheck(refused ? { listUsers: () => deps.listUsers(), canCreateUsers: () => false } : deps, listed, now);
|
|
44492
44564
|
if (refused) after.createRefused = true;
|
|
44493
|
-
|
|
44494
|
-
|
|
44495
|
-
|
|
44496
|
-
|
|
44497
|
-
|
|
44565
|
+
return { results: settleResults(results, after), check: after };
|
|
44566
|
+
}
|
|
44567
|
+
function effectiveStaffList(roster, intake, draftText) {
|
|
44568
|
+
if (roster && teamBoxDecides(roster)) return { ...listedStaff(roster), source: "team" };
|
|
44569
|
+
if (intake?.submitted) return { ...listedStaff(void 0, intake.text), source: "intake" };
|
|
44570
|
+
if (draftText && draftText.trim()) return { ...listedStaff(void 0, draftText), source: "draft" };
|
|
44571
|
+
return { people: [], skipped: [] };
|
|
44572
|
+
}
|
|
44573
|
+
function creationsFor(people) {
|
|
44574
|
+
return people.map((p) => {
|
|
44575
|
+
const c = personToCreateInput(p);
|
|
44576
|
+
return { email: normEmail(c.email), firstName: c.firstName, lastName: c.lastName, role: c.role, ...p.role ? { job: p.role } : {} };
|
|
44577
|
+
});
|
|
44578
|
+
}
|
|
44579
|
+
function parseShownCreations(raw) {
|
|
44580
|
+
if (!Array.isArray(raw)) return void 0;
|
|
44581
|
+
const out = [];
|
|
44582
|
+
for (const r of raw.slice(0, 500)) {
|
|
44583
|
+
if (!r || typeof r !== "object") continue;
|
|
44584
|
+
const x = r;
|
|
44585
|
+
if (typeof x.email !== "string" || !x.email.trim() || x.role !== "admin" && x.role !== "user") continue;
|
|
44586
|
+
out.push({ email: normEmail(x.email), firstName: typeof x.firstName === "string" ? x.firstName : "", lastName: typeof x.lastName === "string" ? x.lastName : "", role: x.role });
|
|
44498
44587
|
}
|
|
44499
|
-
return
|
|
44588
|
+
return out;
|
|
44589
|
+
}
|
|
44590
|
+
function sameCreations(shown, live) {
|
|
44591
|
+
const key2 = (c) => [normEmail(c.email), fullName(c), c.role].join("|");
|
|
44592
|
+
const a = [...shown].map(key2).sort(), b = [...live].map(key2).sort();
|
|
44593
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
44594
|
+
}
|
|
44595
|
+
function staffLevelDiffLine(d) {
|
|
44596
|
+
return `${d.name} is in this account as ${d.account === "admin" ? "an admin" : "a user"} login. The list${d.job ? ` (${d.job})` : ""} would make ${d.listed === "admin" ? "an admin" : "a user"} login. Nothing is changed here: change it in GoHighLevel if it should be different.`;
|
|
44597
|
+
}
|
|
44598
|
+
function staffAddSaid(results) {
|
|
44599
|
+
const made = results.filter((r) => r.status === "created" && r.ok).map((r) => r.name);
|
|
44600
|
+
const bound = results.filter((r) => r.status === "existing" && r.ok).map((r) => r.name);
|
|
44601
|
+
const out = [];
|
|
44602
|
+
if (made.length) out.push(`Added: ${listSentence(made)}.`);
|
|
44603
|
+
if (bound.length) out.push(`${listSentence(bound)} already had a login.`);
|
|
44604
|
+
for (const r of results) if (!r.ok) out.push(r.said);
|
|
44605
|
+
return out;
|
|
44606
|
+
}
|
|
44607
|
+
function staffNotOnTeamLine(name) {
|
|
44608
|
+
return `${name} got a login but is not on the team now: remove it in GoHighLevel if it should not be there.`;
|
|
44609
|
+
}
|
|
44610
|
+
function staffUncheckedLine(results) {
|
|
44611
|
+
const made = results.filter((r) => r.status === "created" && r.ok).map((r) => r.name);
|
|
44612
|
+
return `${made.length ? `Added: ${listSentence(made)}. ` : ""}The team could not be checked again; look at the team list before adding more.`;
|
|
44613
|
+
}
|
|
44614
|
+
function planTeamMatches(planUsers2, people) {
|
|
44615
|
+
const a = [...new Set((planUsers2 ?? []).map((u) => normEmail(u.email)))].sort();
|
|
44616
|
+
const b = [...new Set(people.map((p) => normEmail(p.email)))].sort();
|
|
44617
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
44500
44618
|
}
|
|
44501
|
-
var STAFF_INTAKE_KEYS, STAFF_STEP_ABOUT, STAFF_LIST_HELP, STAFF_HOW_TO_ADD, STAFF_REVIEW_WARNING, STAFF_REVIEW_UNCHECKED, STAFF_GATED_STAGES, ADMIN_ROLE_RE;
|
|
44619
|
+
var STAFF_PENDING_MS, STAFF_INTAKE_KEYS, STAFF_STEP_ABOUT, STAFF_LIST_HELP, STAFF_HOW_TO_ADD, STAFF_REVIEW_WARNING, STAFF_REVIEW_UNCHECKED, STAFF_GATED_STAGES, ADMIN_ROLE_RE, fullName, STAFF_ADMIN_MARK, STAFF_SOURCE_WORDS;
|
|
44502
44620
|
var init_staff_first = __esm({
|
|
44503
44621
|
"src/staff-first.ts"() {
|
|
44504
44622
|
"use strict";
|
|
44505
44623
|
init_normalizer();
|
|
44506
44624
|
init_user_provisioning();
|
|
44507
44625
|
init_ghl_click_paths();
|
|
44626
|
+
STAFF_PENDING_MS = 10 * 60 * 1e3;
|
|
44508
44627
|
STAFF_INTAKE_KEYS = { staff: "staff_members", notify: "notify_name", calls: "calls_name" };
|
|
44509
44628
|
STAFF_STEP_ABOUT = "Calendars and lead alerts need real people. List everyone who takes appointments and everyone who should hear about new leads. We check this client's account for each email address before anything is built, so no calendar or alert is built with a blank name to fix later.";
|
|
44510
44629
|
STAFF_LIST_HELP = "One person per line: name, email, role. For example: Jane Smith, jane@yourclinic.com, Front desk. If it is just you, add yourself.";
|
|
@@ -44513,6 +44632,13 @@ var init_staff_first = __esm({
|
|
|
44513
44632
|
STAFF_REVIEW_UNCHECKED = "We have not checked this client's staff yet. Build will not start until everyone who takes appointments is in the account. Step 7, Staff, checks them.";
|
|
44514
44633
|
STAFF_GATED_STAGES = /* @__PURE__ */ new Set([3]);
|
|
44515
44634
|
ADMIN_ROLE_RE = /\b(owner|founder|admin|administrator|ceo|principal|director)\b/i;
|
|
44635
|
+
fullName = (c) => [c.firstName, c.lastName === "-" ? "" : c.lastName].filter(Boolean).join(" ");
|
|
44636
|
+
STAFF_ADMIN_MARK = "Admin login";
|
|
44637
|
+
STAFF_SOURCE_WORDS = {
|
|
44638
|
+
team: "This list is from Your team in step 5.",
|
|
44639
|
+
intake: "This list is from the client's intake.",
|
|
44640
|
+
draft: "This list is from your intake draft: the client's intake has no team on it yet."
|
|
44641
|
+
};
|
|
44516
44642
|
}
|
|
44517
44643
|
});
|
|
44518
44644
|
|
|
@@ -50704,12 +50830,12 @@ var init_build_steps = __esm({
|
|
|
50704
50830
|
"Command OS has built and checked the account. You sign off here. The list below says what you can switch on now and what waits until its approval comes through."
|
|
50705
50831
|
];
|
|
50706
50832
|
BUILD_STEPS_SCRIPT = String.raw`
|
|
50707
|
-
let bsChange={},bsErr={},bsBusy={},bsStaff={},bsSeen={},bsDraft={},bsNote={},bsInSteps=false,bsMoreOpen={},bsInfo={};
|
|
50833
|
+
let bsStaffSaid={},bsChange={},bsErr={},bsBusy={},bsStaff={},bsSeen={},bsDraft={},bsNote={},bsInSteps=false,bsMoreOpen={},bsInfo={};
|
|
50708
50834
|
/* A link with ?client=<id>&reconnect=1 (or the older #client=<id>&reconnect=1) opens that client's reconnect box (round-6 review). */
|
|
50709
50835
|
try{for(const hp of [new URLSearchParams(location.search),new URLSearchParams(location.hash.slice(1))])if(hp.get("reconnect")==="1"&&hp.get("client"))bsMoreOpen[hp.get("client")]=true}catch(e){}
|
|
50710
50836
|
async function bsPost(url,body){let r,j=null;try{r=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)})}catch(e){return {ok:false,error:"Command OS did not answer. Check it is still running, then try again."}}
|
|
50711
50837
|
try{j=await r.json()}catch(e){}
|
|
50712
|
-
if(!r.ok||!j||j.ok===false)return {ok:false,error:(j&&j.error)||"That did not go through, and nothing was changed.",missing:j&&j.missing,reason:j&&j.reason};
|
|
50838
|
+
if(!r.ok||!j||j.ok===false)return {ok:false,error:(j&&j.error)||"That did not go through, and nothing was changed.",missing:j&&j.missing,reason:j&&j.reason,check:j&&j.check,said:j&&j.said,results:j&&j.results};
|
|
50713
50839
|
return j}
|
|
50714
50840
|
function bsInputs(c,state,run){const id=c.locationId;const st=(((state.clients||{})[id])||{stages:[]}).stages||[];
|
|
50715
50841
|
const stale=((((state.buildViews||{})[id])||{}).stale||[]).map(x=>x.stage);const acc=((state.accounts||{})[id])||{};
|
|
@@ -50870,6 +50996,8 @@ function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
|
50870
50996
|
const BS_KIND={users:"staff member",pipelines:"pipeline",customFields:"custom field",tags:"tag",customValues:"custom value",calendars:"calendar",forms:"form",workflows:"workflow",emails:"email",sms:"text message",emailTemplates:"email template",smsTemplates:"text message template"};
|
|
50871
50997
|
let bsAllow={};
|
|
50872
50998
|
function bsPause(c,state,pv,box){const id=c.locationId;const key="confirm:"+id;
|
|
50999
|
+
/* A list from before 3.90.1 names no logins: out of date, made again before anything can be confirmed. */
|
|
51000
|
+
if(!Array.isArray(pv.logins)){box.appendChild(mk("p","cds-callout warn","This list is out of date. Start the build again to see the current one."));bsRun(c,state,lastRun||{},3,"Start the build",box);return}
|
|
50873
51001
|
/* Ticks belong to one list: a new list starts unticked, and only keys on the list shown are sent (review of a38ebe1). */
|
|
50874
51002
|
if(!bsAllow[id]||bsAllow[id].fp!==pv.fingerprint)bsAllow[id]={fp:pv.fingerprint,keys:{}};const allow=bsAllow[id].keys;
|
|
50875
51003
|
box.appendChild(mk("h3","","Before Build writes anything"));
|
|
@@ -50877,6 +51005,10 @@ function bsPause(c,state,pv,box){const id=c.locationId;const key="confirm:"+id;
|
|
|
50877
51005
|
const kinds=Object.entries(pv.createByType||{}).filter(x=>x[1]>0).map(x=>x[1]+" "+(BS_KIND[x[0]]||x[0])+(x[1]===1?"":"s"));
|
|
50878
51006
|
box.appendChild(mk("h4","","Will create: "+pv.createCount+" new "+plural(pv.createCount,"item")));
|
|
50879
51007
|
if(kinds.length)box.appendChild(mk("p","muted",kinds.join(", ")+"."));
|
|
51008
|
+
/* The logins it would make, each named with the job the team list gives and "Admin login" where it is one
|
|
51009
|
+
(3.90.1, Codex on ad610635), before Confirm and build can be pressed. */
|
|
51010
|
+
if((pv.logins||[]).length){const sc=(state.staffChecks||{})[id]||{};const jobOf=e=>{const p=[...(sc.listed||[]),...(sc.matched||[]),...(sc.missing||[])].find(x=>String(x.email).trim().toLowerCase()===String(e).trim().toLowerCase());return p&&p.role?p.role:""};
|
|
51011
|
+
const ul=mk("ul","cds-list bs-logins");for(const u of pv.logins){const li=mk("li","");li.appendChild(mk("span","",u.name+" ("+u.email+")"+(jobOf(u.email)?" · "+jobOf(u.email):"")));if(u.role==="admin")li.appendChild(chip("info",BS_ADMIN_MARK));ul.appendChild(li)}box.appendChild(ul)}
|
|
50880
51012
|
box.appendChild(mk("h4","","Will use what is already there: "+pv.reuse.length));
|
|
50881
51013
|
if(pv.reuse.length){const ul=mk("ul","cds-list");for(const e of pv.reuse)ul.appendChild(mk("li",""," "+(BS_KIND[e.type]||e.type)+" \u201c"+e.name+"\u201d, used as it is"));box.appendChild(ul)}
|
|
50882
51014
|
else box.appendChild(mk("p","muted","Nothing already in the account is used."));
|
|
@@ -50985,22 +51117,28 @@ function bsReconnect(c,pr){const id=c.locationId;const key="reconnect:"+id;const
|
|
|
50985
51117
|
function bsOpenReconnect(id){bsMoreOpen[id]=true;render(lastState,lastRun);const el=document.getElementById("bs-more-"+id);if(el)try{el.scrollIntoView({block:"start"})}catch(e){}}
|
|
50986
51118
|
/* Step 7: everyone from step 5 must be a user in the client's GoHighLevel account. */
|
|
50987
51119
|
function bsStaffStep(c,state,box){const id=c.locationId;const sc=(state.staffChecks||{})[id]||bsStaff[id];const key="staff:"+id;
|
|
51120
|
+
/* What the last Add them for me did (3.90.1): who was added, who already had a login, any login made for someone
|
|
51121
|
+
no longer on the team. Shown in every staff state (Codex on 516081d8); the list below is the team as it is now. */
|
|
51122
|
+
if((bsStaffSaid[id]||[]).length){const said=mk("div","cds-callout info cds-stack bs-staffsaid");said.setAttribute("role","status");for(const line of bsStaffSaid[id])said.appendChild(mk("p","",line));box.appendChild(said)}
|
|
50988
51123
|
if(!sc&&bsErr[key]&&!bsBusy[key]){bsErrLine(key,box);box.appendChild(bsBtn("Check again",()=>{bsBusy[key]=true;delete bsErr[key];bsCheckStaff(id);render(lastState,lastRun)}));return}
|
|
50989
51124
|
if(!sc&&!bsBusy[key]){bsBusy[key]=true;bsCheckStaff(id)}
|
|
50990
51125
|
if(!sc){box.appendChild(mk("p","muted","Checking this client's GoHighLevel account for your team…"));return}
|
|
50991
|
-
if(sc.status==="needs_list"){box.appendChild(mk("p","","Nobody is on the team yet. Add them under Your team in step 5, Intake."));const go=bsBtn("Open step 5",()=>{bsChange[id+":4"]=true;render(lastState,lastRun)},true);box.appendChild(go);return}
|
|
51126
|
+
if(sc.status==="needs_list"){box.appendChild(mk("p","","Nobody is on the team yet. Add them under Your team in step 5, Intake."));const go=bsBtn("Open step 5",()=>{bsChange[id+":4"]=true;render(lastState,lastRun)},true);box.appendChild(go);bsErrLine(key,box);return}
|
|
50992
51127
|
if(sc.status==="unreadable"){box.appendChild(mk("p","cds-callout warn","We could not read this client's staff in GoHighLevel. Try Check again in a minute. If it keeps happening, reconnect this client."));box.appendChild(bsBtn("Reconnect this client",()=>bsOpenReconnect(id),true))}
|
|
51128
|
+
if(sc.source&&BS_STAFF_SOURCE[sc.source])box.appendChild(mk("p","muted bs-source",BS_STAFF_SOURCE[sc.source]));
|
|
50993
51129
|
const ul=mk("ul","cds-list bs-people");
|
|
50994
|
-
for(const m of (sc.matched||[])){const li=mk("li","");li.appendChild(chip("ok","In the account"));li.appendChild(mk("span",""," "+m.name+" · "+m.email));ul.appendChild(li)}
|
|
50995
|
-
for(const p of (sc.missing||[])){const li=mk("li","");li.appendChild(chip("warn","Not added yet"));li.appendChild(mk("span",""," "+p.name+" · "+p.email+" "));
|
|
51130
|
+
for(const m of (sc.matched||[])){const li=mk("li","");li.appendChild(chip("ok","In the account"));li.appendChild(mk("span",""," "+m.name+" · "+m.email+(m.role?" · "+m.role:"")));ul.appendChild(li)}
|
|
51131
|
+
for(const p of (sc.missing||[])){const li=mk("li","");const w=(sc.waiting||[]).find(x=>x.email===p.email);if(!w)li.appendChild(chip("warn","Not added yet"));const cr=(sc.creations||[]).find(x=>x.email===String(p.email).trim().toLowerCase());if(cr&&cr.role==="admin")li.appendChild(chip("info",BS_ADMIN_MARK));li.appendChild(mk("span",""," "+p.name+" · "+p.email+(p.role?" · "+p.role:"")+" "));if(w)li.appendChild(mk("span","muted",w.line+" "));
|
|
50996
51132
|
for(const [lab,val] of [["Copy name",p.name],["Copy email",p.email]]){const cp=mk("button","cds-link",lab);cp.type="button";cp.onclick=async()=>{try{await navigator.clipboard.writeText(val);cp.textContent="Copied"}catch(e){cp.textContent=val}};li.appendChild(cp);li.appendChild(document.createTextNode(" "))}
|
|
50997
51133
|
ul.appendChild(li)}
|
|
50998
51134
|
box.appendChild(ul);
|
|
51135
|
+
for(const d of (sc.levelDiffs||[]))box.appendChild(mk("p","cds-callout warn bs-level",d.line));
|
|
51136
|
+
|
|
50999
51137
|
if((sc.missing||[]).length){
|
|
51000
|
-
if(sc.canAddForYou&&!sc.createRefused){box.appendChild(mk("p","","We can add them to this client's GoHighLevel account for you.
|
|
51001
|
-
box.appendChild(bsBtn(bsBusy[key+":add"]?"Adding…":"Add them for me",async()=>{bsBusy[key+":add"]=true;delete bsErr[key];render(lastState,lastRun);const j=await bsPost("/api/staff/add",{locationId:id});bsBusy[key+":add"]=false;if(!j.ok)bsErr[key]=j.error;else bsStaff[id]=j.check;load(true)}))}
|
|
51138
|
+
if(sc.canAddForYou&&!sc.createRefused){if((sc.creations||[]).length){box.appendChild(mk("p","","We can add them to this client's GoHighLevel account for you. GoHighLevel should email each new person an invitation. Let your client know, and check with them that it arrived."));
|
|
51139
|
+
box.appendChild(bsBtn(bsBusy[key+":add"]?"Adding…":"Add them for me",async()=>{bsBusy[key+":add"]=true;delete bsErr[key];render(lastState,lastRun);const j=await bsPost("/api/staff/add",{locationId:id,shown:sc.creations||[]});bsBusy[key+":add"]=false;bsStaffSaid[id]=[...(j.ok?(j.said||[]):[]),...(j.results||[]).filter(x=>x.generatedPassword).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately; it is not saved anywhere.")];if(!j.ok){bsErr[key]=j.error;if(j.check)bsStaff[id]=j.check}else bsStaff[id]=j.check;load(true)}))}}
|
|
51002
51140
|
else{const how=mk("ol","bs-how");for(const t of BS_HOW_TO_ADD)how.appendChild(mk("li","",t));box.appendChild(mk("p","","GoHighLevel only lets you add people in its own screens on your plan. Do this once for each person marked Not added yet:"));box.appendChild(how)}}
|
|
51003
|
-
const again=bsBtn(bsBusy[key]?"Checking…":"Check again",()=>{bsBusy[key]=true;bsCheckStaff(id);render(lastState,lastRun)},sc.status==="ready");box.appendChild(again);
|
|
51141
|
+
const again=bsBtn(bsBusy[key]?"Checking…":"Check again",()=>{bsBusy[key]=true;delete bsStaffSaid[id];bsCheckStaff(id);render(lastState,lastRun)},sc.status==="ready");box.appendChild(again);
|
|
51004
51142
|
bsErrLine(key,box)}
|
|
51005
51143
|
async function bsCheckStaff(id){const key="staff:"+id;let j=null;try{j=await(await fetch("/api/staff/check",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:id})})).json()}catch(e){}
|
|
51006
51144
|
bsBusy[key]=false;if(j&&j.ok){bsStaff[id]=j.check;delete bsErr[key]}else bsErr[key]=(j&&j.error)||"We could not check this client's staff. Try Check again.";load(true)}
|
|
@@ -51096,7 +51234,9 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
|
|
|
51096
51234
|
for(let i=0;i<n;i++){const ck=id+":"+i;
|
|
51097
51235
|
/* A Change closes itself once that step is done with a different answer (review of 64da59d, LOW 15). */
|
|
51098
51236
|
if(bsChange[ck]&&pr.done[i]&&typeof bsChange[ck]==="string"&&bsSummary(i,c,state)!==bsChange[ck])delete bsChange[ck];
|
|
51099
|
-
|
|
51237
|
+
/* Step 7 stays open while what the last Add them for me said, or its refusal, is there to read (3.90.1). */
|
|
51238
|
+
const staffNews=i===6&&(((bsStaffSaid[id]||[]).length>0)||Boolean(bsErr["staff:"+id]));
|
|
51239
|
+
const li=mk("li","bs-step");li.id="step-"+(i+1);const open=(i===pr.current)||Boolean(bsChange[ck])||staffNews;
|
|
51100
51240
|
const num=mk("span","bs-num",String(i+1));num.setAttribute("aria-hidden","true");li.appendChild(num);const main=mk("div","bs-main");li.appendChild(main);
|
|
51101
51241
|
if(open){li.className+=" bs-now";main.appendChild(mk("div","bs-kicker","Step "+(i+1)+" of "+n+(i===pr.current?" · do this now":" · changing")));main.appendChild(mk("h3","bs-title",BS_NAMES[i]));main.appendChild(mk("p","muted",BS_ABOUT[i]));
|
|
51102
51242
|
if(inp.parked&&i>=2){main.appendChild(mk("p","cds-callout info","This is a "+TYPE_LABELS[typeOf(state,id)].toLowerCase()+" account, so nothing is built into it. Change step 2 to Client or Practice sandbox to build it."))}
|
|
@@ -56513,9 +56653,14 @@ function previewFingerprint(p) {
|
|
|
56513
56653
|
locationId: p.locationId,
|
|
56514
56654
|
planHash: p.planHash,
|
|
56515
56655
|
reuse: p.reuse.map((e) => e.key).sort(),
|
|
56516
|
-
change: p.change.map((e) => [e.key, (e.changes ?? []).map(stableChange).sort(), e.state ?? ""]).sort((a, b) => String(a[0]).localeCompare(String(b[0])))
|
|
56656
|
+
change: p.change.map((e) => [e.key, (e.changes ?? []).map(stableChange).sort(), e.state ?? ""]).sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
56657
|
+
// 3.90.1 (Codex on e4734bc4): the logins the confirmation names are part of what was confirmed.
|
|
56658
|
+
logins: (p.logins ?? []).map((l) => `${l.email.trim().toLowerCase()}|${l.name}|${l.role}`).sort()
|
|
56517
56659
|
});
|
|
56518
56660
|
}
|
|
56661
|
+
function previewNamesLogins(preview) {
|
|
56662
|
+
return Boolean(preview && Array.isArray(preview.logins));
|
|
56663
|
+
}
|
|
56519
56664
|
function composePreview(input) {
|
|
56520
56665
|
const reuse = [];
|
|
56521
56666
|
const change = [];
|
|
@@ -56540,7 +56685,7 @@ function composePreview(input) {
|
|
|
56540
56685
|
else reuse.push({ key: key2, type: item.type, name: item.name, id: item.existingId });
|
|
56541
56686
|
}
|
|
56542
56687
|
if (input.board) change.push({ key: `${BOARD_KEY_PREFIX}${input.board.id}`, type: "customValues", name: input.board.name, id: input.board.id, changes: [BOARD_CHANGE] });
|
|
56543
|
-
const core = { locationId: input.locationId, planHash: input.planHash, reuse, change };
|
|
56688
|
+
const core = { locationId: input.locationId, planHash: input.planHash, reuse, change, logins: [...input.logins ?? []] };
|
|
56544
56689
|
return { v: 1, recordKey: input.recordKey, at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(), createCount, createByType, ...core, fingerprint: previewFingerprint(core) };
|
|
56545
56690
|
}
|
|
56546
56691
|
function allowedWorkflowIds(allow) {
|
|
@@ -56560,6 +56705,7 @@ function boardAllowed(preview, confirm2) {
|
|
|
56560
56705
|
return Boolean(confirm2 && confirm2.fingerprint === preview.fingerprint && confirm2.allow.includes(board.key));
|
|
56561
56706
|
}
|
|
56562
56707
|
function confirmMatches(confirm2, preview) {
|
|
56708
|
+
if (!previewNamesLogins(preview)) return false;
|
|
56563
56709
|
if (!confirm2 || confirm2.fingerprint !== preview.fingerprint || !Array.isArray(confirm2.allow)) return false;
|
|
56564
56710
|
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
56565
56711
|
return confirm2.allow.every((k) => changeKeys.has(k));
|
|
@@ -57046,7 +57192,6 @@ __export(dashboard_exports, {
|
|
|
57046
57192
|
overlayPath: () => overlayPath,
|
|
57047
57193
|
parseIntakeShape: () => parseIntakeShape,
|
|
57048
57194
|
planClaimChecks: () => planClaimChecks,
|
|
57049
|
-
prefillTeamAnswers: () => prefillTeamAnswers,
|
|
57050
57195
|
proPickerHtml: () => proPickerHtml,
|
|
57051
57196
|
queueScanFilter: () => queueScanFilter,
|
|
57052
57197
|
queueScanOnce: () => queueScanOnce,
|
|
@@ -57092,6 +57237,7 @@ __export(dashboard_exports, {
|
|
|
57092
57237
|
stateSaveFs: () => stateSaveFs,
|
|
57093
57238
|
statusFromThisRun: () => statusFromThisRun,
|
|
57094
57239
|
syncVerifyVerdict: () => syncVerifyVerdict,
|
|
57240
|
+
teamAnswersFromRoster: () => teamAnswersFromRoster,
|
|
57095
57241
|
uniqueDocName: () => uniqueDocName,
|
|
57096
57242
|
unknownCapabilityHtml: () => unknownCapabilityHtml,
|
|
57097
57243
|
unverifiedStaffNames: () => unverifiedStaffNames,
|
|
@@ -57365,6 +57511,12 @@ function offeredFeaturesFor(state2, recordKey) {
|
|
|
57365
57511
|
function boughtWordingFor(state2, recordKey) {
|
|
57366
57512
|
return { programFollowUp: programFollowUpName(presetWorkflowsFor(state2, recordKey)) };
|
|
57367
57513
|
}
|
|
57514
|
+
function savedPlanTeamIsCurrent(sp, answers, questions) {
|
|
57515
|
+
const users = sp.plan && typeof sp.plan === "object" ? sp.plan.users : void 0;
|
|
57516
|
+
const planUsers2 = Array.isArray(users) ? users.filter((u) => Boolean(u) && typeof u.email === "string") : [];
|
|
57517
|
+
const label2 = questions.find((q3) => q3.key === STAFF_INTAKE_KEYS.staff)?.label;
|
|
57518
|
+
return planTeamMatches(planUsers2, listedStaff(void 0, intakeAnswer(answers, STAFF_INTAKE_KEYS.staff, label2)).people);
|
|
57519
|
+
}
|
|
57368
57520
|
function documentScopeFor(state2, locationId2, savedPlan) {
|
|
57369
57521
|
const sc = state2.scope?.[locationId2];
|
|
57370
57522
|
const offered = offeredFeaturesFor(state2, locationId2);
|
|
@@ -57530,9 +57682,11 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
57530
57682
|
// changed selection composes the plan fresh from the preset (delta round 3, T-M4).
|
|
57531
57683
|
// Rehearsal 5 (BUG-2026-09-30-10): Build is told who the staff check matched in this account.
|
|
57532
57684
|
...stage === 3 ? { verifiedStaff: verifiedStaffNames(state2.staffChecks?.[locationId2]) } : {},
|
|
57685
|
+
// A saved plan is reused only for the team the rule gives now (3.90.1): one saved under an older team (a draft,
|
|
57686
|
+
// or before Your team decided) is made again, never built with someone the member was not shown.
|
|
57533
57687
|
savedPlan: (() => {
|
|
57534
57688
|
const sp = stage === 3 ? readSavedPlan(rk) : null;
|
|
57535
|
-
return sp && sp.inputHash === meta.inputHash && savedPlanMatchesScope(sp, state2.scope?.[rk]) ? { planId: sp.planId, savedAt: sp.savedAt, summary: planSummary(sp) } : void 0;
|
|
57689
|
+
return sp && sp.inputHash === meta.inputHash && savedPlanMatchesScope(sp, state2.scope?.[rk]) && savedPlanTeamIsCurrent(sp, meta.normalizedAnswers, composed) ? { planId: sp.planId, savedAt: sp.savedAt, summary: planSummary(sp) } : void 0;
|
|
57536
57690
|
})(),
|
|
57537
57691
|
textSnippets: Boolean(state2.textSnippets?.[rk])
|
|
57538
57692
|
};
|
|
@@ -59411,13 +59565,19 @@ a.textContent="Open Your agency";box.appendChild(a);return box}
|
|
|
59411
59565
|
person listed here is found in the account by email. The server reads the account every
|
|
59412
59566
|
time; nothing on this panel is taken as proof on its own. */
|
|
59413
59567
|
let staffBusy={},staffSaid={},staffErr={};
|
|
59568
|
+
/* Whether Add them for me would make this person an admin (the check's own creations, never guessed here). */
|
|
59569
|
+
function staffAdminFor(check,m){const cr=((check&&check.creations)||[]).find(x=>x.email===String(m.email).trim().toLowerCase());return Boolean(cr&&cr.role==="admin")}
|
|
59414
59570
|
function staffCard(c,state){const box=document.createElement("section");box.className="cds-panel ipanel staffstep";box.id="staff-"+c.locationId;
|
|
59415
59571
|
const check=(state.staffChecks||{})[c.locationId];const roster=(state.staffRoster||{})[c.locationId]||{};
|
|
59416
59572
|
const h=document.createElement("h3");h.className="cds-panel-title";h.textContent=check&&check.status==="ready"?"Your staff are in place":"Add your staff";box.appendChild(h);
|
|
59417
59573
|
box.appendChild(mk("p","",check?check.headline+". "+check.body:"Calendars and lead alerts need real people. List everyone who takes appointments and everyone who should hear about new leads. We check this client's account for each email address before anything is built."));
|
|
59418
|
-
|
|
59419
|
-
|
|
59420
|
-
|
|
59574
|
+
/* One listing in every state (3.90.1, Codex on 516081d8): where the list came from, then each person with their
|
|
59575
|
+
job, and "Admin login" on anyone Add them for me would make an admin, before it can be pressed. */
|
|
59576
|
+
if(check&&((check.matched||[]).length||(check.missing||[]).length)){if(check.source&&BS_STAFF_SOURCE[check.source])box.appendChild(mk("p","small",BS_STAFF_SOURCE[check.source]));
|
|
59577
|
+
const ul=mk("ul","cds-list");
|
|
59578
|
+
for(const m of check.matched||[]){const li=mk("li");li.appendChild(mk("span","ttl",m.name+" ("+m.email+")"+(m.role?" \xB7 "+m.role:"")));const end=mk("span","end");end.appendChild(chip("ok","In the account"));li.appendChild(end);ul.appendChild(li)}
|
|
59579
|
+
for(const m of check.missing||[]){const li=mk("li");li.appendChild(mk("span","ttl",m.name+" ("+m.email+")"+(m.role?" \xB7 "+m.role:"")));const end=mk("span","end");const w=(check.waiting||[]).find(x=>x.email===m.email);if(staffAdminFor(check,m))end.appendChild(chip("info",BS_ADMIN_MARK));end.appendChild(w?mk("span","small",w.line):chip("warn","Not in the account yet"));li.appendChild(end);ul.appendChild(li)}
|
|
59580
|
+
box.appendChild(ul)}
|
|
59421
59581
|
for(const line of (check&&check.steps)||[])box.appendChild(mk("p","small",line));
|
|
59422
59582
|
if(check&&check.createRefused)box.appendChild(mk("p","small","GoHighLevel does not let this agency add people from outside GoHighLevel, so please add them yourself."));
|
|
59423
59583
|
if(check&&check.howToAdd)box.appendChild(mk("p","small",check.howToAdd));
|
|
@@ -59434,13 +59594,18 @@ function staffCard(c,state){const box=document.createElement("section");box.clas
|
|
|
59434
59594
|
/* Rehearsal 3: creating the logins asks in a panel under the button, and what GoHighLevel said (with any
|
|
59435
59595
|
one-time password) stays on the page until Done, never in confirm() or alert(). The passwords live
|
|
59436
59596
|
only in this page's memory. */
|
|
59437
|
-
if(check&&check.status==="needs_staff"&&check.canAddForYou){const add=document.createElement("button");add.className="cds-btn";add.textContent="Add them for me";const addKey="staffadd:"+c.locationId;
|
|
59597
|
+
if(check&&check.status==="needs_staff"&&check.canAddForYou&&(check.creations||[]).length){const add=document.createElement("button");add.className="cds-btn";add.textContent="Add them for me";const addKey="staffadd:"+c.locationId;
|
|
59438
59598
|
add.onclick=()=>{let r=null;icShow(add,addKey,{title:"Create GoHighLevel logins in "+c.name+"?",
|
|
59439
|
-
text:[(check.missing||[]).map(m=>m.name+" ("+m.email+")").join(", ")+".","GoHighLevel
|
|
59599
|
+
text:[(check.missing||[]).filter(m=>!(check.waiting||[]).some(x=>x.email===m.email)).map(m=>m.name+" ("+m.email+")"+(m.role?" \xB7 "+m.role:"")+(staffAdminFor(check,m)?" \xB7 "+BS_ADMIN_MARK:"")).join(", ")+".","GoHighLevel should email each new person an invitation. We then read the account back to confirm they arrived."],
|
|
59440
59600
|
ok:"Create the logins",busyLabel:"Creating the logins\u2026",
|
|
59441
|
-
run:async()=>{r=await(await clientPageRequest("/api/staff/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId})})).json();
|
|
59442
|
-
|
|
59443
|
-
|
|
59601
|
+
run:async()=>{r=await(await clientPageRequest("/api/staff/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,shown:check.creations||[]})})).json();
|
|
59602
|
+
/* Any answer closes this confirmation (it showed the list as it was) and its lines go to the panel's own box,
|
|
59603
|
+
which stays through the redraw until Done (3.90.1, Codex on e4734bc4); a refusal's team is drawn at once. */
|
|
59604
|
+
const lines=!r?[clientPageError("That did not go through. Click Check again to see where things stand.")]:[...(r.ok===false?[clientPageError(r.error||"That did not go through. Click Check again to see where things stand.")]:[]),...(r.said||[]),...(r.results||[]).filter(x=>x.generatedPassword).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately; it is not saved anywhere.")];
|
|
59605
|
+
if(lines.length)staffSaid[c.locationId]=lines;
|
|
59606
|
+
if(r&&r.check&&lastState)lastState.staffChecks=Object.assign({},lastState.staffChecks,{[c.locationId]:r.check});
|
|
59607
|
+
return ""},
|
|
59608
|
+
after:async()=>{delete icOpen[addKey];load()}})};
|
|
59444
59609
|
btns.appendChild(icAfter(add,addKey))}
|
|
59445
59610
|
box.appendChild(btns);
|
|
59446
59611
|
if(staffErr[c.locationId])showClientError(staffErr[c.locationId],box);
|
|
@@ -60529,7 +60694,7 @@ function renderPage(state,run){if(state.roster)mergeRoster(state.roster);syncRev
|
|
|
60529
60694
|
d.appendChild(mk("summary","","Show the "+parked.length+" template and hidden "+plural(parked.length,"account")));
|
|
60530
60695
|
d.appendChild(listRows(rows.slice(board.length),"clients-parked"));list.appendChild(d)}
|
|
60531
60696
|
m.appendChild(list)}
|
|
60532
|
-
const BS_TEAM_KEYS=${scriptJson(Object.values(STAFF_INTAKE_KEYS))};const BS_TOPICS=${scriptJson(Object.fromEntries(GO_LIVE_TOPICS.map((g) => [g.key, g.title])))};const BS_NAMES=${scriptJson(STEP_NAMES)};const BS_ABOUT=${scriptJson(STEP_ABOUT)};const BS_HOW_TO_ADD=${scriptJson(BS_HOW_TO_ADD)};const BS_TYPE_HELP=${scriptJson(BS_TYPE_HELP)};
|
|
60697
|
+
const BS_TEAM_KEYS=${scriptJson(Object.values(STAFF_INTAKE_KEYS))};const BS_ADMIN_MARK=${scriptJson(STAFF_ADMIN_MARK)};const BS_STAFF_SOURCE=${scriptJson(STAFF_SOURCE_WORDS)};const BS_TOPICS=${scriptJson(Object.fromEntries(GO_LIVE_TOPICS.map((g) => [g.key, g.title])))};const BS_NAMES=${scriptJson(STEP_NAMES)};const BS_ABOUT=${scriptJson(STEP_ABOUT)};const BS_HOW_TO_ADD=${scriptJson(BS_HOW_TO_ADD)};const BS_TYPE_HELP=${scriptJson(BS_TYPE_HELP)};
|
|
60533
60698
|
const stepProgress=(${stepProgress.toString()});
|
|
60534
60699
|
let bsHideAssess=false;
|
|
60535
60700
|
${BUILD_STEPS_SCRIPT}
|
|
@@ -60959,15 +61124,31 @@ async function runDashboard(argv) {
|
|
|
60959
61124
|
const tok = registry2.getToken(locationId2);
|
|
60960
61125
|
return tok?.apiKey ? new GHLClient({ apiKey: tok.apiKey, locationId: locationId2 }) : void 0;
|
|
60961
61126
|
};
|
|
60962
|
-
const
|
|
61127
|
+
const staffListedSaved = (locationId2) => {
|
|
60963
61128
|
const st = readCockpitState();
|
|
60964
61129
|
const questions = composeQuestionSet({ industry: st.accounts?.[locationId2]?.industry, agency: readOverlay() });
|
|
60965
61130
|
const label2 = questions.find((q3) => q3.key === STAFF_INTAKE_KEYS.staff)?.label;
|
|
61131
|
+
const saved = st.intakeCompleted?.[locationId2]?.answers ?? st.intakeInputs?.[locationId2]?.answers;
|
|
61132
|
+
return effectiveStaffList(
|
|
61133
|
+
st.staffRoster?.[locationId2],
|
|
61134
|
+
saved ? { submitted: true, text: intakeAnswer(saved, STAFF_INTAKE_KEYS.staff, label2) } : void 0,
|
|
61135
|
+
intakeAnswer(st.intakeDrafts?.[locationId2]?.answers, STAFF_INTAKE_KEYS.staff, label2)
|
|
61136
|
+
);
|
|
61137
|
+
};
|
|
61138
|
+
void staffListedSaved;
|
|
61139
|
+
const staffListedLive = async (locationId2) => {
|
|
61140
|
+
const st = readCockpitState();
|
|
60966
61141
|
const roster2 = st.staffRoster?.[locationId2];
|
|
60967
|
-
if (roster2 && String(roster2.lines ?? "").trim()) return
|
|
60968
|
-
|
|
60969
|
-
|
|
60970
|
-
|
|
61142
|
+
if (roster2 && String(roster2.lines ?? "").trim()) return effectiveStaffList(roster2, void 0, void 0);
|
|
61143
|
+
const c = clientFor(locationId2);
|
|
61144
|
+
if (!c) throw new Error("This client is not connected yet.");
|
|
61145
|
+
const snap = await readIntakeSnapshot(c, locationId2);
|
|
61146
|
+
if (snap.status === "unavailable") throw new Error("We could not check this client's staff. Try Check again.");
|
|
61147
|
+
const questions = composeQuestionSet({ industry: st.accounts?.[locationId2]?.industry, agency: readOverlay() });
|
|
61148
|
+
const label2 = questions.find((q3) => q3.key === STAFF_INTAKE_KEYS.staff)?.label;
|
|
61149
|
+
return effectiveStaffList(
|
|
61150
|
+
void 0,
|
|
61151
|
+
{ submitted: snap.status === "received", text: intakeAnswer(snap.answers, STAFF_INTAKE_KEYS.staff, label2) },
|
|
60971
61152
|
intakeAnswer(st.intakeDrafts?.[locationId2]?.answers, STAFF_INTAKE_KEYS.staff, label2)
|
|
60972
61153
|
);
|
|
60973
61154
|
};
|
|
@@ -60988,18 +61169,47 @@ async function runDashboard(argv) {
|
|
|
60988
61169
|
}
|
|
60989
61170
|
};
|
|
60990
61171
|
};
|
|
61172
|
+
const staffAddChain = /* @__PURE__ */ new Map();
|
|
61173
|
+
const oneStaffAddAtATime = (locationId2, run) => {
|
|
61174
|
+
const tail = (staffAddChain.get(locationId2) ?? Promise.resolve()).then(run, run).catch(() => void 0);
|
|
61175
|
+
staffAddChain.set(locationId2, tail);
|
|
61176
|
+
void tail.then(() => {
|
|
61177
|
+
if (staffAddChain.get(locationId2) === tail) staffAddChain.delete(locationId2);
|
|
61178
|
+
});
|
|
61179
|
+
return tail;
|
|
61180
|
+
};
|
|
61181
|
+
const holdStaff = (locationId2, email, v) => {
|
|
61182
|
+
const latest = readCockpitState();
|
|
61183
|
+
const mine = { ...latest.staffPending?.[locationId2] ?? {} };
|
|
61184
|
+
if (v) mine[normEmail(email)] = v;
|
|
61185
|
+
else delete mine[normEmail(email)];
|
|
61186
|
+
writeCockpitState({ ...latest, staffPending: { ...latest.staffPending, [locationId2]: mine } });
|
|
61187
|
+
};
|
|
61188
|
+
const endSeenHolds = (locationId2, check) => {
|
|
61189
|
+
const latest = readCockpitState();
|
|
61190
|
+
const was = latest.staffPending?.[locationId2];
|
|
61191
|
+
if (!was) return;
|
|
61192
|
+
const now = pendingSeen(was, check);
|
|
61193
|
+
if (Object.keys(now).length !== Object.keys(was).length) writeCockpitState({ ...latest, staffPending: { ...latest.staffPending, [locationId2]: now } });
|
|
61194
|
+
};
|
|
60991
61195
|
const saveStaffCheck = (locationId2, check) => {
|
|
60992
61196
|
const latest = readCockpitState();
|
|
60993
61197
|
writeCockpitState({ ...latest, staffChecks: { ...latest.staffChecks, [locationId2]: check } });
|
|
60994
61198
|
};
|
|
60995
61199
|
const staffCheckFor = async (locationId2, save = true) => {
|
|
60996
|
-
const
|
|
60997
|
-
|
|
61200
|
+
const listed = await staffListedLive(locationId2);
|
|
61201
|
+
const check = applyStaffPending(await readStaffCheck(staffDepsFor(locationId2), listed), readCockpitState().staffPending?.[locationId2], Date.now());
|
|
61202
|
+
if (listed.source) check.source = listed.source;
|
|
61203
|
+
if (save) {
|
|
61204
|
+
saveStaffCheck(locationId2, check);
|
|
61205
|
+
endSeenHolds(locationId2, check);
|
|
61206
|
+
}
|
|
60998
61207
|
return check;
|
|
60999
61208
|
};
|
|
61000
61209
|
staffGateRef = async (locationId2, stage) => {
|
|
61001
61210
|
if (!STAFF_GATED_STAGES.has(stage)) return null;
|
|
61002
|
-
|
|
61211
|
+
const check = await staffCheckFor(locationId2).catch(() => checkStaff({ people: [], skipped: [] }, { error: "unreadable" }, { canAddForYou: false }));
|
|
61212
|
+
return staffGateReason(check, STAGES2[stage] === "Staged Build" ? "Build" : STAGES2[stage]);
|
|
61003
61213
|
};
|
|
61004
61214
|
const readAgencySnapshots = async () => {
|
|
61005
61215
|
const agencyKey = registry2.getAgencyKey();
|
|
@@ -61900,25 +62110,82 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
61900
62110
|
return;
|
|
61901
62111
|
}
|
|
61902
62112
|
if (req.method === "POST" && url === "/api/staff/add") {
|
|
61903
|
-
readBody(req, res,
|
|
62113
|
+
readBody(req, res, 2e4, async (body) => {
|
|
62114
|
+
let queued = "";
|
|
61904
62115
|
try {
|
|
61905
|
-
const
|
|
61906
|
-
if (typeof
|
|
61907
|
-
|
|
61908
|
-
if (!perm.allowed) throw new Error(perm.reason);
|
|
61909
|
-
const out = await addMissingStaff(staffDepsFor(locationId2), staffListedFor(locationId2));
|
|
61910
|
-
if (out.check.createRefused) {
|
|
61911
|
-
const latest = readCockpitState();
|
|
61912
|
-
writeCockpitState({ ...latest, staffAddRefused: { ...latest.staffAddRefused, [locationId2]: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
61913
|
-
}
|
|
61914
|
-
saveStaffCheck(locationId2, out.check);
|
|
61915
|
-
writeCockpitState(recordStaffAdd(readCockpitState(), locationId2, currentActor()));
|
|
61916
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
61917
|
-
res.end(JSON.stringify({ ok: true, ...out }));
|
|
61918
|
-
} catch (e) {
|
|
61919
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
61920
|
-
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : "bad request" }));
|
|
62116
|
+
const id = JSON.parse(body).locationId;
|
|
62117
|
+
if (typeof id === "string") queued = id;
|
|
62118
|
+
} catch {
|
|
61921
62119
|
}
|
|
62120
|
+
await oneStaffAddAtATime(queued, async () => {
|
|
62121
|
+
try {
|
|
62122
|
+
const { locationId: locationId2, shown: shownRaw } = JSON.parse(body);
|
|
62123
|
+
if (typeof locationId2 !== "string" || !readableAccount(locationId2)) throw new Error("Choose a registered client.");
|
|
62124
|
+
const perm = buildPermission(locationId2, { name: currentRoster().find((r) => r.locationId === locationId2)?.name, approvedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
62125
|
+
if (!perm.allowed) throw new Error(perm.reason);
|
|
62126
|
+
const deps = staffDepsFor(locationId2);
|
|
62127
|
+
const listed = await staffListedLive(locationId2);
|
|
62128
|
+
const live = applyStaffPending(await readStaffCheck(deps, listed), readCockpitState().staffPending?.[locationId2], Date.now());
|
|
62129
|
+
if (listed.source) live.source = listed.source;
|
|
62130
|
+
const shown = parseShownCreations(shownRaw);
|
|
62131
|
+
if (live.status === "unreadable") throw new Error("We could not check this client's staff. Try Check again.");
|
|
62132
|
+
const changed = shown ? sameCreations(shown, live.creations ?? []) ? [] : ["the team changed"] : ["this page did not send the list it showed; reload the page"];
|
|
62133
|
+
if (!shown || changed.length) {
|
|
62134
|
+
saveStaffCheck(locationId2, live);
|
|
62135
|
+
const refusal2 = staleRefusal("the team", changed);
|
|
62136
|
+
res.writeHead(refusal2.status, { "Content-Type": "application/json" });
|
|
62137
|
+
res.end(JSON.stringify({ ...refusal2.body, check: live }));
|
|
62138
|
+
return;
|
|
62139
|
+
}
|
|
62140
|
+
const out = await addMissingStaff({ ...deps, createUser: async (u) => {
|
|
62141
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
62142
|
+
const name = [u.firstName, u.lastName === "-" ? "" : u.lastName].filter(Boolean).join(" ");
|
|
62143
|
+
holdStaff(locationId2, u.email, { name, at, phase: "creating" });
|
|
62144
|
+
const outcome = await deps.createUser(u);
|
|
62145
|
+
if (outcome.ok && outcome.status === "created") holdStaff(locationId2, u.email, { name, at, phase: "created" });
|
|
62146
|
+
else if (outcome.ok || createMadeNothing(outcome)) holdStaff(locationId2, u.email, void 0);
|
|
62147
|
+
return outcome;
|
|
62148
|
+
} }, listed, void 0, live);
|
|
62149
|
+
out.check = applyStaffPending(out.check, readCockpitState().staffPending?.[locationId2], Date.now());
|
|
62150
|
+
if (out.check.createRefused) {
|
|
62151
|
+
const latest = readCockpitState();
|
|
62152
|
+
writeCockpitState({ ...latest, staffAddRefused: { ...latest.staffAddRefused, [locationId2]: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
62153
|
+
}
|
|
62154
|
+
writeCockpitState(recordStaffAdd(readCockpitState(), locationId2, currentActor()));
|
|
62155
|
+
let said = staffAddSaid(out.results);
|
|
62156
|
+
const createdOf = () => out.results.filter((r) => r.status === "created").map((r) => normEmail(r.email));
|
|
62157
|
+
let created = createdOf();
|
|
62158
|
+
const after = await staffListedLive(locationId2).catch(() => null);
|
|
62159
|
+
const reply = (status, body2) => {
|
|
62160
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
62161
|
+
res.end(JSON.stringify(body2));
|
|
62162
|
+
};
|
|
62163
|
+
if (!after) {
|
|
62164
|
+
saveStaffCheck(locationId2, out.check);
|
|
62165
|
+
reply(409, { ok: false, written: created.length > 0, error: staffUncheckedLine(out.results), said, results: out.results, created, check: out.check });
|
|
62166
|
+
return;
|
|
62167
|
+
}
|
|
62168
|
+
const fresh = applyStaffPending(await readStaffCheck(deps, after), readCockpitState().staffPending?.[locationId2], Date.now());
|
|
62169
|
+
if (fresh.status === "unreadable") {
|
|
62170
|
+
saveStaffCheck(locationId2, out.check);
|
|
62171
|
+
reply(409, { ok: false, written: created.length > 0, error: staffUncheckedLine(out.results), said, results: out.results, created, check: out.check });
|
|
62172
|
+
return;
|
|
62173
|
+
}
|
|
62174
|
+
if (after.source) fresh.source = after.source;
|
|
62175
|
+
if (out.check.createRefused) fresh.createRefused = true;
|
|
62176
|
+
fresh.canAddForYou = fresh.canAddForYou && out.check.canAddForYou;
|
|
62177
|
+
saveStaffCheck(locationId2, fresh);
|
|
62178
|
+
endSeenHolds(locationId2, fresh);
|
|
62179
|
+
said = staffAddSaid(settleResults(out.results, fresh));
|
|
62180
|
+
created = createdOf();
|
|
62181
|
+
const listedNow = new Set(after.people.map((p) => normEmail(p.email)));
|
|
62182
|
+
for (const r of out.results) if (r.status === "created" && !listedNow.has(normEmail(r.email))) said.push(staffNotOnTeamLine(r.name));
|
|
62183
|
+
reply(200, { ok: true, said, results: out.results, created, check: fresh });
|
|
62184
|
+
} catch (e) {
|
|
62185
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
62186
|
+
res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : "bad request" }));
|
|
62187
|
+
}
|
|
62188
|
+
});
|
|
61922
62189
|
});
|
|
61923
62190
|
return;
|
|
61924
62191
|
}
|
|
@@ -62099,7 +62366,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
62099
62366
|
inputHash = intakeInput(normalizedAnswers, st0.accounts?.[locationId2]?.industry, composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }));
|
|
62100
62367
|
st0 = { ...st0, intakeInputs: { ...st0.intakeInputs, [locationId2]: { hash: inputHash, answers: normalizedAnswers, status: bound.status, shape: intakeShapeOf(snap) } } };
|
|
62101
62368
|
writeCockpitState(st0);
|
|
62102
|
-
if (stage !== 4) normalizedAnswers =
|
|
62369
|
+
if (stage !== 4) normalizedAnswers = teamAnswersFromRoster(normalizedAnswers, st0.staffRoster?.[locationId2], composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }));
|
|
62103
62370
|
if (stage >= 3) {
|
|
62104
62371
|
assertBuildApproval(st0.planApprovals?.[locationId2], inputHash);
|
|
62105
62372
|
if (stage === 3) assertStaffUnchanged(st0.planApprovals?.[locationId2], st0.staffRoster?.[locationId2]);
|
|
@@ -62235,7 +62502,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
62235
62502
|
const industry = st.accounts?.[locationId2]?.industry;
|
|
62236
62503
|
const questions = composeQuestionSet({ industry, agency: readOverlay() }).map((q3) => ({ key: q3.key, contactTag: q3.field.kind === "standard" ? q3.field.tag : void 0, label: q3.label, sectionLabel: q3.sectionLabel, required: q3.required, options: q3.options ?? null, placeholder: q3.placeholder ?? "", multi: q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS" }));
|
|
62237
62504
|
const completed = st.intakeCompleted?.[locationId2];
|
|
62238
|
-
const answers = completed ? completed.answers : fillBusinessType(
|
|
62505
|
+
const answers = completed ? completed.answers : fillBusinessType(teamAnswersFromRoster(normalizedIntake(snap, st.intakeDrafts?.[locationId2]), st.staffRoster?.[locationId2], questions), industry, questions);
|
|
62239
62506
|
const latestState = readCockpitState();
|
|
62240
62507
|
const bound = boundIntake(latestState, locationId2, snap);
|
|
62241
62508
|
const submittedAnswers = bound.answers;
|
|
@@ -62841,7 +63108,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
62841
63108
|
const current = readCockpitState();
|
|
62842
63109
|
if (!current.buildApproved?.[rk]) throw new Error("Approve building (step 8) before confirming the build.");
|
|
62843
63110
|
const preview = readBuildPreview(rk);
|
|
62844
|
-
if (!preview || typeof fingerprint !== "string" || preview.fingerprint !== fingerprint) throw new Error("This list is out of date. Start the build again to see the current one.");
|
|
63111
|
+
if (!preview || !previewNamesLogins(preview) || typeof fingerprint !== "string" || preview.fingerprint !== fingerprint) throw new Error("This list is out of date. Start the build again to see the current one.");
|
|
62845
63112
|
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
62846
63113
|
const allowed = Array.isArray(allow) ? [...new Set(allow.filter((k) => typeof k === "string"))] : [];
|
|
62847
63114
|
if (allowed.some((k) => !changeKeys.has(k))) throw new Error("Only items on the list can be changed.");
|
|
@@ -78723,7 +78990,8 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
78723
78990
|
return jsonResponse({ ok: false, phase: "confirm", error: `Command OS could not read this account's custom values (${e instanceof Error ? e.message : String(e)}), so it cannot list what Build would change. Nothing was written. Run Build again.` });
|
|
78724
78991
|
}
|
|
78725
78992
|
const { changes: workflowChanges, states: workflowStates } = await previewWorkflowChanges(deps, result, ours, saved?.calendarPending);
|
|
78726
|
-
const
|
|
78993
|
+
const logins = (typedPlan.users ?? []).filter((u) => result.items.some((i) => i.ref === u.ref && i.type === "users" && i.status === "would_create")).map((u) => ({ name: [u.firstName, u.lastName === "-" ? "" : u.lastName].filter(Boolean).join(" "), email: u.email, role: u.role }));
|
|
78994
|
+
const preview = composePreview({ locationId: activeLocation, recordKey, planHash: contentHash(typedPlan), items: result.items, ours, workflowChanges, workflowStates, board, logins });
|
|
78727
78995
|
try {
|
|
78728
78996
|
saveBuildPreview(preview);
|
|
78729
78997
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elitedcs/ghl-mcp",
|
|
3
|
-
"version": "3.90.
|
|
3
|
+
"version": "3.90.1",
|
|
4
4
|
"mcpName": "io.github.drjerryrelth/ghl-command",
|
|
5
5
|
"description": "GoHighLevel MCP Server for Claude. 250 tools — 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.",
|
|
6
6
|
"main": "dist/index.js",
|