@elitedcs/ghl-mcp 3.94.1 → 3.94.2
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 +14 -0
- package/README.md +1 -1
- package/dist/index.js +1027 -925
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41101,6 +41101,39 @@ var init_result_text = __esm({
|
|
|
41101
41101
|
}
|
|
41102
41102
|
});
|
|
41103
41103
|
|
|
41104
|
+
// src/stage-failure.ts
|
|
41105
|
+
function claudeSignedOut(error, lines = []) {
|
|
41106
|
+
return [typeof error === "string" ? error : "", ...lines].some((l) => CLAUDE_SIGNED_OUT.test(String(l)));
|
|
41107
|
+
}
|
|
41108
|
+
function failureNoteFor(stage, outcome, lines, already) {
|
|
41109
|
+
if (stage === 4 || outcome.ok || already || outcome.summary || outcome.issues?.length) return null;
|
|
41110
|
+
const s = stageFailureSummary(stage, outcome.error, lines);
|
|
41111
|
+
return { ...outcome, summary: s, issues: [], plain: { summary: s, issues: [] } };
|
|
41112
|
+
}
|
|
41113
|
+
function stageFailureSummary(stage, error, lines = []) {
|
|
41114
|
+
const step = STEP_NAME[stage] ?? "This step";
|
|
41115
|
+
const again = AGAIN[stage];
|
|
41116
|
+
const safe = READ_ONLY_STAGES.has(stage) ? " Nothing in the account was changed." : "";
|
|
41117
|
+
if (claudeSignedOut(error, lines)) {
|
|
41118
|
+
const before = READ_ONLY_STAGES.has(stage) || [typeof error === "string" ? error : "", ...lines].some((l) => /\bso nothing was run\b/.test(String(l)));
|
|
41119
|
+
if (!before) return `${step} stopped: Claude Code is signed out on this computer. Sign in to Claude Code again (type /login in Claude Code), then review the account before clicking ${again ? `"${again}"` : "the button"} again.`;
|
|
41120
|
+
return `${step} did not run: Claude Code is signed out on this computer. Sign in to Claude Code again (type /login in Claude Code), then click ${again ? `"${again}"` : "the button"} again.${safe || " Nothing in the account was changed."}`;
|
|
41121
|
+
}
|
|
41122
|
+
const why = typeof error === "string" && error.trim() ? ` ${clean3(error).replace(/[.]?$/, ".")}` : "";
|
|
41123
|
+
return `${step} did not finish.${why}${safe}${READ_ONLY_STAGES.has(stage) && again ? ` You can click "${again}" again.` : ""}`;
|
|
41124
|
+
}
|
|
41125
|
+
var CLAUDE_SIGNED_OUT, READ_ONLY_STAGES, STEP_NAME, AGAIN, clean3;
|
|
41126
|
+
var init_stage_failure = __esm({
|
|
41127
|
+
"src/stage-failure.ts"() {
|
|
41128
|
+
"use strict";
|
|
41129
|
+
CLAUDE_SIGNED_OUT = /\bnot logged in\b|please run \/login/i;
|
|
41130
|
+
READ_ONLY_STAGES = /* @__PURE__ */ new Set([1, 4]);
|
|
41131
|
+
STEP_NAME = { 1: "The Brief", 3: "Build", 4: "Verify" };
|
|
41132
|
+
AGAIN = { 1: "Write the Brief", 3: "Build", 4: "Verify" };
|
|
41133
|
+
clean3 = (s) => s.replace(/\s+/g, " ").replace(/\s*—\s*/g, ". ").trim().slice(0, 300);
|
|
41134
|
+
}
|
|
41135
|
+
});
|
|
41136
|
+
|
|
41104
41137
|
// src/claude-cli.ts
|
|
41105
41138
|
function parseVersion(text3) {
|
|
41106
41139
|
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text3 || "");
|
|
@@ -41233,12 +41266,13 @@ function runSmoke(bin, timeoutMs = 9e4) {
|
|
|
41233
41266
|
child.on("close", () => {
|
|
41234
41267
|
clearTimeout(timer);
|
|
41235
41268
|
if (/RESULT:\s*\{/.test(texts.pick())) done({ ok: true, version, model, costUsd });
|
|
41269
|
+
else if (claudeSignedOut(stderrTail, texts.texts)) done({ ok: false, signedOut: true, version, model, costUsd });
|
|
41236
41270
|
else done({
|
|
41237
41271
|
ok: false,
|
|
41238
41272
|
version,
|
|
41239
41273
|
model,
|
|
41240
41274
|
costUsd,
|
|
41241
|
-
error: stderrTail.trim().slice(-160) || "
|
|
41275
|
+
error: stderrTail.trim().slice(-160) || "The engine did not answer in the format every stage depends on."
|
|
41242
41276
|
});
|
|
41243
41277
|
});
|
|
41244
41278
|
});
|
|
@@ -41268,6 +41302,16 @@ async function ensureEngineReady(bin, onNote = () => {
|
|
|
41268
41302
|
!prior ? "First run on this computer. Checking Claude Code answers the way Command OS expects\u2026" : prior.version !== version ? `Claude Code changed on this computer (${prior.version} \u2192 ${version}). Checking it still speaks to Command OS before running anything\u2026` : "The last run used a different Claude model than the one checked. Checking it still speaks to Command OS before running anything\u2026"
|
|
41269
41303
|
);
|
|
41270
41304
|
const smoke = await runSmoke(bin);
|
|
41305
|
+
if (!smoke.ok && smoke.signedOut) {
|
|
41306
|
+
return {
|
|
41307
|
+
ok: false,
|
|
41308
|
+
version,
|
|
41309
|
+
smoked: true,
|
|
41310
|
+
smokeCostUsd: smoke.costUsd,
|
|
41311
|
+
smokeModel: smoke.model,
|
|
41312
|
+
reason: 'Claude Code is signed out on this computer (it answered "Not logged in"), so nothing was run. Sign in to Claude Code again (type /login in Claude Code), then run the step again.'
|
|
41313
|
+
};
|
|
41314
|
+
}
|
|
41271
41315
|
if (!smoke.ok) {
|
|
41272
41316
|
return {
|
|
41273
41317
|
ok: false,
|
|
@@ -41293,6 +41337,7 @@ var init_claude_cli = __esm({
|
|
|
41293
41337
|
init_credentials_store();
|
|
41294
41338
|
init_claude_locate();
|
|
41295
41339
|
init_result_text();
|
|
41340
|
+
init_stage_failure();
|
|
41296
41341
|
MIN_CLAUDE_VERSION = "2.1.0";
|
|
41297
41342
|
SMOKE_PROMPT = 'Reply with exactly this one line and nothing else: RESULT: {"ok":true,"summary":"engine check"}';
|
|
41298
41343
|
}
|
|
@@ -43035,41 +43080,12 @@ var init_handoff_run = __esm({
|
|
|
43035
43080
|
}
|
|
43036
43081
|
});
|
|
43037
43082
|
|
|
43038
|
-
// src/stage-failure.ts
|
|
43039
|
-
function claudeSignedOut(error, lines = []) {
|
|
43040
|
-
return [typeof error === "string" ? error : "", ...lines].some((l) => CLAUDE_SIGNED_OUT.test(String(l)));
|
|
43041
|
-
}
|
|
43042
|
-
function failureNoteFor(stage, outcome, lines, already) {
|
|
43043
|
-
if (stage === 4 || outcome.ok || already || outcome.summary || outcome.issues?.length) return null;
|
|
43044
|
-
const s = stageFailureSummary(stage, outcome.error, lines);
|
|
43045
|
-
return { ...outcome, summary: s, issues: [], plain: { summary: s, issues: [] } };
|
|
43046
|
-
}
|
|
43047
|
-
function stageFailureSummary(stage, error, lines = []) {
|
|
43048
|
-
const step = STEP_NAME[stage] ?? "This step";
|
|
43049
|
-
const again = AGAIN[stage];
|
|
43050
|
-
const safe = READ_ONLY_STAGES.has(stage) ? " Nothing in the account was changed." : "";
|
|
43051
|
-
if (claudeSignedOut(error, lines)) {
|
|
43052
|
-
return `${step} did not run: Claude Code is signed out on this computer. Sign in to Claude Code again (type /login in Claude Code), then click ${again ? `"${again}"` : "the button"} again.${safe}`;
|
|
43053
|
-
}
|
|
43054
|
-
const why = typeof error === "string" && error.trim() ? ` ${clean3(error).replace(/[.]?$/, ".")}` : "";
|
|
43055
|
-
return `${step} did not finish.${why}${safe}${READ_ONLY_STAGES.has(stage) && again ? ` You can click "${again}" again.` : ""}`;
|
|
43056
|
-
}
|
|
43057
|
-
var CLAUDE_SIGNED_OUT, READ_ONLY_STAGES, STEP_NAME, AGAIN, clean3;
|
|
43058
|
-
var init_stage_failure = __esm({
|
|
43059
|
-
"src/stage-failure.ts"() {
|
|
43060
|
-
"use strict";
|
|
43061
|
-
CLAUDE_SIGNED_OUT = /\bnot logged in\b|please run \/login/i;
|
|
43062
|
-
READ_ONLY_STAGES = /* @__PURE__ */ new Set([1, 4]);
|
|
43063
|
-
STEP_NAME = { 1: "The Brief", 3: "Build", 4: "Verify" };
|
|
43064
|
-
AGAIN = { 1: "Write the Brief", 3: "Build", 4: "Verify" };
|
|
43065
|
-
clean3 = (s) => s.replace(/\s+/g, " ").replace(/\s*—\s*/g, ". ").trim().slice(0, 300);
|
|
43066
|
-
}
|
|
43067
|
-
});
|
|
43068
|
-
|
|
43069
43083
|
// src/member-progress.ts
|
|
43070
|
-
function memberProgress(lines, running, ok, stage) {
|
|
43084
|
+
function memberProgress(lines, running, ok, stage, paused) {
|
|
43071
43085
|
const failed = stage !== void 0 && READ_ONLY_STAGES.has(stage) ? "This step did not finish. It changes nothing in the account, so it is safe to run again." : "This step did not finish. Review the account before retrying.";
|
|
43072
|
-
const
|
|
43086
|
+
const ranNothing = stage !== void 0 && READ_ONLY_STAGES.has(stage) || lines.some((l) => /\bso nothing was run\b/.test(l));
|
|
43087
|
+
const signedOut = !running && ok !== true && claudeSignedOut("", lines) ? ranNothing ? "This step did not run: Claude Code is signed out on this computer. Nothing in the account was changed." : "This step stopped: Claude Code is signed out on this computer. Sign in to Claude Code again, then review the account before running it again." : "";
|
|
43088
|
+
const result = [running ? "Command OS is working on this step." : ok === true ? "This step finished. Review the result below." : paused ? "Build stopped before writing anything. Check its list below, then confirm it or stop there." : signedOut || failed];
|
|
43073
43089
|
if (lines.some((l) => /writing.*review/i.test(l))) result.push("Command OS is preparing the client review.");
|
|
43074
43090
|
if (lines.some((l) => /plain-English version/i.test(l))) result.push("Command OS is preparing the result for review.");
|
|
43075
43091
|
for (const line4 of lines) if (/^(This step used |This step: cost not reported[.]|Checking Claude Code still works with Command OS used |Plain rewrite: (cost not reported|\$|under ))/.test(line4)) result.push(line4.replace(/—/g, "."));
|
|
@@ -43177,7 +43193,7 @@ function verifyPublishingText(text3, allowed) {
|
|
|
43177
43193
|
return sentences(text3).filter((s) => allowed || !publishing.test(s)).join(" ");
|
|
43178
43194
|
}
|
|
43179
43195
|
function switchedOffLine(count4) {
|
|
43180
|
-
return count4 ? `${count4} ${count4 === 1 ? "automation is" : "automations are"} built and switched off until
|
|
43196
|
+
return count4 ? `${count4} ${count4 === 1 ? "automation is" : "automations are"} built and switched off until you switch ${count4 === 1 ? "it" : "them"} on.` : "";
|
|
43181
43197
|
}
|
|
43182
43198
|
function verifyReadback(records) {
|
|
43183
43199
|
const readback = records.readback ?? records.deps.map((d) => ({
|
|
@@ -43817,6 +43833,605 @@ var init_go_live = __esm({
|
|
|
43817
43833
|
}
|
|
43818
43834
|
});
|
|
43819
43835
|
|
|
43836
|
+
// src/build-contract.ts
|
|
43837
|
+
function contentHash2(value) {
|
|
43838
|
+
const canonical5 = (v) => Array.isArray(v) ? v.map(canonical5) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => [k, canonical5(v[k])])) : v;
|
|
43839
|
+
return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify(canonical5(value)) ?? "null").digest("hex");
|
|
43840
|
+
}
|
|
43841
|
+
function staffListHash(roster) {
|
|
43842
|
+
return contentHash2({ lines: (roster?.lines ?? "").trim(), notifyName: roster?.notifyName ?? "", callsName: roster?.callsName ?? "" });
|
|
43843
|
+
}
|
|
43844
|
+
function assertStaffUnchanged(approval, roster) {
|
|
43845
|
+
if ((approval?.staffHash ?? staffListHash(null)) !== staffListHash(roster)) throw new Error(STAFF_CHANGED);
|
|
43846
|
+
}
|
|
43847
|
+
function assertBuildApproval(approval, inputHash) {
|
|
43848
|
+
if (!approval?.at) throw new Error("Approve building before running Build.");
|
|
43849
|
+
if (!approval.inputHash || inputHash && approval.inputHash !== inputHash) throw new Error(INTAKE_CHANGED);
|
|
43850
|
+
}
|
|
43851
|
+
function assertApprovedPlan(saved, approval, inputHash, build) {
|
|
43852
|
+
if (!saved?.plan || !saved.contentHash && !build?.inputHash) throw new Error("Run Build before verifying.");
|
|
43853
|
+
assertBuildApproval(approval, inputHash);
|
|
43854
|
+
if (saved.contentHash && contentHash2(saved.plan) !== saved.contentHash) throw new Error("The saved build plan changed. Run Build again before verifying.");
|
|
43855
|
+
const builtInput = build?.inputHash ?? saved.inputHash;
|
|
43856
|
+
if (!builtInput || builtInput !== approval.inputHash || inputHash && builtInput !== inputHash || saved.inputHash && saved.inputHash !== builtInput) throw new Error(INTAKE_CHANGED);
|
|
43857
|
+
}
|
|
43858
|
+
function readPlanApproval(locationId2, base = appDataDir()) {
|
|
43859
|
+
try {
|
|
43860
|
+
return JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.join)(base, "cockpit-state.json"), "utf8")).planApprovals?.[locationId2];
|
|
43861
|
+
} catch {
|
|
43862
|
+
return void 0;
|
|
43863
|
+
}
|
|
43864
|
+
}
|
|
43865
|
+
function stageFreshness(inputHash, saved, approval, build, verify2, brief) {
|
|
43866
|
+
let from = 7;
|
|
43867
|
+
let reason3 = "";
|
|
43868
|
+
const builtInput = build?.inputHash ?? saved?.inputHash;
|
|
43869
|
+
const buildChanged = Boolean(builtInput && builtInput !== inputHash);
|
|
43870
|
+
const buildReason = "The intake changed since Build. Run Build and Verify again.";
|
|
43871
|
+
const planHash = saved ? saved.contentHash ?? contentHash2(saved.plan) : void 0;
|
|
43872
|
+
const reviewedInput = brief?.inputHash ?? approval?.inputHash ?? saved?.inputHash;
|
|
43873
|
+
if (reviewedInput && reviewedInput !== inputHash) {
|
|
43874
|
+
from = 1;
|
|
43875
|
+
reason3 = INTAKE_CHANGED;
|
|
43876
|
+
} else if (approval && approval.inputHash !== inputHash) {
|
|
43877
|
+
from = 2;
|
|
43878
|
+
reason3 = "The intake review changed. Approve building again.";
|
|
43879
|
+
} else if (buildChanged) {
|
|
43880
|
+
from = 3;
|
|
43881
|
+
reason3 = buildReason;
|
|
43882
|
+
} else if (verify2 && (!planHash || contentHash2(saved.plan) !== planHash || verify2.inputHash !== planHash || !build?.hash || verify2.buildHash !== build.hash)) {
|
|
43883
|
+
from = 4;
|
|
43884
|
+
reason3 = "The build changed after this check. Run Verify again.";
|
|
43885
|
+
}
|
|
43886
|
+
return Array.from({ length: Math.max(0, 7 - from) }, (_, i) => ({ stage: from + i, status: "stale", reason: buildChanged && from + i >= 3 ? buildReason : reason3 }));
|
|
43887
|
+
}
|
|
43888
|
+
function planDependencies(plan) {
|
|
43889
|
+
const result = [];
|
|
43890
|
+
for (const section2 of ["users", "pipelines", "customFields", "fields", "tags", "customValues", "calendars", "forms", "workflows"]) {
|
|
43891
|
+
for (const item of plan?.[section2] ?? []) result.push({ ref: item.ref || `${section2}:${item.name}`, name: item.name || item.title || [item.firstName, item.lastName].filter(Boolean).join(" ") || item.ref, instruction: `Check ${item.name || item.ref} against the approved plan.` });
|
|
43892
|
+
}
|
|
43893
|
+
for (const h of plan?.handoffs ?? []) result.push({ ref: h.ref, name: h.title || h.name || h.ref, instruction: h.instruction });
|
|
43894
|
+
return result;
|
|
43895
|
+
}
|
|
43896
|
+
function normalizedIntake(snap, draft) {
|
|
43897
|
+
return draft?.savedAt && (!snap.answeredAt || draft.savedAt > snap.answeredAt) ? { ...snap.answers, ...draft.answers } : snap.answers ?? draft?.answers ?? {};
|
|
43898
|
+
}
|
|
43899
|
+
function intakeInput(answers, industry, questions) {
|
|
43900
|
+
return contentHash2({ answers, industry, questions });
|
|
43901
|
+
}
|
|
43902
|
+
function shortenPlanLine(text3, limit = 360) {
|
|
43903
|
+
if (text3.length <= limit) return text3;
|
|
43904
|
+
const tokens2 = text3.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
|
|
43905
|
+
let end = "";
|
|
43906
|
+
for (const token2 of tokens2) {
|
|
43907
|
+
const next = end ? `${end} ${token2}` : token2;
|
|
43908
|
+
if (next.length > limit - 1) {
|
|
43909
|
+
if (!end) return token2.length === text3.length ? text3 : `${token2}\u2026`;
|
|
43910
|
+
break;
|
|
43911
|
+
}
|
|
43912
|
+
end = next;
|
|
43913
|
+
}
|
|
43914
|
+
return `${end}\u2026`;
|
|
43915
|
+
}
|
|
43916
|
+
function executablePlanLines(saved) {
|
|
43917
|
+
const p = saved.plan;
|
|
43918
|
+
const preview = saved.preview;
|
|
43919
|
+
const lines = [];
|
|
43920
|
+
const kinds = {
|
|
43921
|
+
users: "staff member",
|
|
43922
|
+
pipelines: "pipeline",
|
|
43923
|
+
stages: "pipeline stage",
|
|
43924
|
+
customFields: "field",
|
|
43925
|
+
fields: "field",
|
|
43926
|
+
tags: "tag",
|
|
43927
|
+
customValues: "custom value",
|
|
43928
|
+
calendars: "calendar",
|
|
43929
|
+
forms: "form",
|
|
43930
|
+
funnels: "funnel",
|
|
43931
|
+
workflows: "workflow",
|
|
43932
|
+
emails: "email message",
|
|
43933
|
+
sms: "text message",
|
|
43934
|
+
handoffs: "handoff",
|
|
43935
|
+
actions: "workflow step",
|
|
43936
|
+
user: "staff member",
|
|
43937
|
+
stage: "pipeline stage",
|
|
43938
|
+
cv: "custom value",
|
|
43939
|
+
email: "email message",
|
|
43940
|
+
email_template: "email template",
|
|
43941
|
+
sms_template: "text message template",
|
|
43942
|
+
pipeline: "pipeline",
|
|
43943
|
+
field: "field",
|
|
43944
|
+
tag: "tag",
|
|
43945
|
+
workflow: "workflow",
|
|
43946
|
+
form: "form",
|
|
43947
|
+
calendar: "calendar",
|
|
43948
|
+
handoff: "handoff",
|
|
43949
|
+
funnel: "funnel"
|
|
43950
|
+
};
|
|
43951
|
+
const words2 = (s) => s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_.-]+/g, " ").trim();
|
|
43952
|
+
const kindOf2 = (s) => kinds[s] || words2(s).replace(/s$/, "") || "plan object";
|
|
43953
|
+
const names = /* @__PURE__ */ new Map();
|
|
43954
|
+
const objects = /* @__PURE__ */ new Map();
|
|
43955
|
+
const label2 = (x, kind) => x.name?.trim() || x.title?.trim() || [x.firstName, x.lastName].filter(Boolean).join(" ") || `Unnamed ${kind}`;
|
|
43956
|
+
const collect = (value, kind = "plan object") => {
|
|
43957
|
+
if (Array.isArray(value)) {
|
|
43958
|
+
for (const item of value) collect(item, kind);
|
|
43959
|
+
return;
|
|
43960
|
+
}
|
|
43961
|
+
if (!value || typeof value !== "object") return;
|
|
43962
|
+
if (typeof value.ref === "string") {
|
|
43963
|
+
names.set(value.ref, label2(value, kind));
|
|
43964
|
+
objects.set(value.ref, value);
|
|
43965
|
+
}
|
|
43966
|
+
for (const [key2, child] of Object.entries(value)) collect(child, key2 === "templates" ? "message template" : kinds[key2] || kind);
|
|
43967
|
+
};
|
|
43968
|
+
collect(p);
|
|
43969
|
+
for (const kind of ["emails", "sms"]) for (const t of p?.templates?.[kind] ?? []) names.set(t.ref, label2(t, kind === "emails" ? "email template" : "text message template"));
|
|
43970
|
+
const escapeRE = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43971
|
+
const refs3 = [...names.keys()].sort((a, b) => b.length - a.length);
|
|
43972
|
+
const refPattern = refs3.length ? new RegExp(`(?<![\\w./:@])(?:${refs3.map(escapeRE).join("|")})(?![\\w]|\\.[\\w])`, "g") : void 0;
|
|
43973
|
+
const missingRefPattern = /(?<![\w./:@])(user|pipeline|stage|field|tag|workflow|form|calendar|email_template|sms_template|email|sms|cv|handoff|funnel)\.[a-z0-9_]+(?![\w]|\.[\w])/g;
|
|
43974
|
+
const resolve15 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
|
|
43975
|
+
const renderRefs = (s) => resolve15(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
|
|
43976
|
+
const mergeLabel = (field2) => {
|
|
43977
|
+
const [group, ...parts] = field2.trim().split(".");
|
|
43978
|
+
const key2 = parts.join(".");
|
|
43979
|
+
const candidates = group === "custom_values" ? p?.customValues : group === "contact" ? [...p?.customFields ?? [], ...p?.fields ?? []] : [];
|
|
43980
|
+
const slug3 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
43981
|
+
const match = (candidates ?? []).find((x) => [x.key, x.fieldKey, x.ref, x.name].some((v) => typeof v === "string" && (v === field2 || slug3(v.split(".").pop()) === slug3(key2))));
|
|
43982
|
+
if (match?.name) return match.name;
|
|
43983
|
+
if (group === "contact") return `contact's ${key2 === "name" ? "full name" : words2(key2)}`;
|
|
43984
|
+
if (group === "custom_values") return words2(key2);
|
|
43985
|
+
return words2(field2);
|
|
43986
|
+
};
|
|
43987
|
+
const plain = (s) => renderRefs(s.replace(/<a\b[^>]*href\s*=\s*(["'])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi, (_all, _quote, href, text3) => `${text3} (${href})`).replace(/<[^>]*>/g, " ").replace(/&(?:amp|nbsp|quot|apos|lt|gt|mdash|ndash);|&#(?:x[0-9a-f]+|[0-9]+);/gi, (entity) => {
|
|
43988
|
+
const named = { "&": "&", " ": " ", """: '"', "'": "'", "<": "<", ">": ">", "—": " ", "–": " " };
|
|
43989
|
+
if (named[entity.toLowerCase()] !== void 0) return named[entity.toLowerCase()];
|
|
43990
|
+
const code = entity[2].toLowerCase() === "x" ? parseInt(entity.slice(3), 16) : parseInt(entity.slice(2), 10);
|
|
43991
|
+
return code <= 1114111 ? String.fromCodePoint(code) : entity;
|
|
43992
|
+
}).replace(/{{\s*([^{}]+?)\s*}}/g, (_all, field2) => `[${mergeLabel(field2)}]`)).replace(/\bMCP\b/g, "GHL Command").replace(/Agency OS/g, "Command OS").replace(/\s*—\s*/g, " ").replace(/\s+/g, " ").replace(/\s+([.,;!?])/g, "$1").trim();
|
|
43993
|
+
const FIELD_KIND = { TEXT: "text", LARGE_TEXT: "long text", NUMERICAL: "number", MONETORY: "money", MONETARY: "money", DATE: "date", SINGLE_OPTIONS: "one choice from a list", MULTIPLE_OPTIONS: "several choices from a list", CHECKBOX: "checkboxes", RADIO: "one choice", PHONE: "phone number", EMAIL: "email address", FILE_UPLOAD: "file upload", TEXTBOX_LIST: "list of text boxes", SIGNATURE: "signature" };
|
|
43994
|
+
const HIDDEN_KEYS = /* @__PURE__ */ new Set(["ref", "position", "order", "sortOrder"]);
|
|
43995
|
+
const keyWords = (k) => k === "dataType" ? "kind" : words2(k.replace(/Refs?$/, ""));
|
|
43996
|
+
const show = (v, key2 = "") => typeof v === "string" ? names.get(v) || (/Refs?$/.test(key2) ? `Unnamed ${kindOf2(key2.replace(/Refs?$/, "").replace(/^teamMember$/, "user").replace(/^template$/, "message template"))}` : key2 === "dataType" ? FIELD_KIND[v] ?? words2(v).toLowerCase() : plain(key2 === "mergeTags" ? `{{${v}}}` : key2 === "type" ? words2(v) : v)) : Array.isArray(v) ? v.map((value) => show(value, key2)).join(", ") : v && typeof v === "object" ? ((e) => e.length === 1 && e[0][0] === "name" ? show(e[0][1], "name") : e.map(([k, value]) => `${keyWords(k)}: ${show(value, k)}`).join("; "))(Object.entries(v).filter(([k]) => !HIDDEN_KEYS.has(k))) : String(v ?? "");
|
|
43997
|
+
const sentence = (s) => {
|
|
43998
|
+
const text3 = plain(s);
|
|
43999
|
+
return text3 && !/[.!?…]$/.test(text3) ? `${text3}.` : text3;
|
|
44000
|
+
};
|
|
44001
|
+
const messageCopy = (t) => [t.subject ? `Subject: ${sentence(t.subject)}` : "", t.body || t.html ? `Message: ${sentence(t.body || t.html)}` : t.bodyOutline ? `Message outline: ${sentence(t.bodyOutline)}` : ""].filter(Boolean).join(" ");
|
|
44002
|
+
for (const section2 of ["users", "pipelines", "customFields", "fields", "tags", "customValues", "calendars", "forms", "funnels"]) for (const x of p?.[section2] ?? []) {
|
|
44003
|
+
const existing = preview?.items?.find((i) => i.ref === x.ref)?.status === "existing";
|
|
44004
|
+
lines.push(`${existing ? "Reuse" : "Create"} ${kindOf2(section2)}: ${label2(x, kindOf2(section2))}. ${show(Object.fromEntries(Object.entries(x).filter(([k]) => !["ref", "name"].includes(k))))}`);
|
|
44005
|
+
}
|
|
44006
|
+
for (const w of p?.workflows ?? []) {
|
|
44007
|
+
lines.push(`Workflow: ${label2(w, "workflow")}. ${show(w.trigger || w.triggers || "")}`);
|
|
44008
|
+
for (const [i, a] of (w.actions ?? []).entries()) {
|
|
44009
|
+
const attrs = a.attributes || a;
|
|
44010
|
+
const recipient = attrs.to || attrs.userRef || attrs.assignedTo || attrs.notification?.selectedUser || (/email|sms/.test(a.type || "") ? "The contact in this workflow" : /notification|assign_user|task/.test(a.type || "") ? "not set yet (choose who gets this)" : "");
|
|
44011
|
+
const asset = objects.get(a.emailRef || a.smsRef || attrs.emailRef || attrs.smsRef);
|
|
44012
|
+
const message = objects.get(a.templateRef || attrs.templateRef || asset?.templateRef) || asset;
|
|
44013
|
+
const actionName = a.name || words2(a.type || a.action || "Workflow step").replace(/^./, (c) => c.toUpperCase());
|
|
44014
|
+
lines.push(`${i + 1}. ${actionName}.${recipient ? ` Recipient: ${show(recipient, attrs.userRef ? "userRef" : "")}.` : ""} ${sentence(show(attrs))}${message ? ` ${messageCopy(message)}` : ""}`);
|
|
44015
|
+
}
|
|
44016
|
+
}
|
|
44017
|
+
for (const kind of ["emails", "sms"]) for (const t of p?.templates?.[kind] ?? []) lines.push(`Message template: ${label2(t, kind === "emails" ? "email template" : "text message template")}. Recipient: The contact in the workflow using this template. ${messageCopy(t)}`);
|
|
44018
|
+
for (const d of saved.dependencies ?? planDependencies(p)) lines.push(`Dependency: ${names.get(d.ref) || (d.name && d.name !== d.ref ? renderRefs(d.name) : `Unnamed ${kinds[d.ref.split(".")[0]] || "plan object"}`)}. ${d.instruction || "Needs evidence from Verify or a visible manual task."}`);
|
|
44019
|
+
for (const h of preview?.handoffs ?? []) if (!h.met) lines.push(`Unresolved: ${h.title || names.get(h.ref) || "Unnamed handoff"}. ${h.instruction || ""}`);
|
|
44020
|
+
for (const w of preview?.workflows ?? []) for (const m of [...w.manual ?? [], ...w.needsContent ?? []]) lines.push(`Unresolved: ${w.name || names.get(w.ref) || "Unnamed workflow"}. ${m.reason}`);
|
|
44021
|
+
return lines.map(sentence);
|
|
44022
|
+
}
|
|
44023
|
+
var import_node_crypto17, import_node_fs6, import_node_path5, STAFF_CHANGED, INTAKE_CHANGED;
|
|
44024
|
+
var init_build_contract = __esm({
|
|
44025
|
+
"src/build-contract.ts"() {
|
|
44026
|
+
"use strict";
|
|
44027
|
+
import_node_crypto17 = require("node:crypto");
|
|
44028
|
+
import_node_fs6 = require("node:fs");
|
|
44029
|
+
import_node_path5 = require("node:path");
|
|
44030
|
+
init_credentials_store();
|
|
44031
|
+
STAFF_CHANGED = "Your staff list changed after you approved the build. Review and approve again.";
|
|
44032
|
+
INTAKE_CHANGED = "The intake changed. Run Brief again and approve the new review.";
|
|
44033
|
+
}
|
|
44034
|
+
});
|
|
44035
|
+
|
|
44036
|
+
// src/build-facts.ts
|
|
44037
|
+
function customValueKeyOf(name2) {
|
|
44038
|
+
return name2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
44039
|
+
}
|
|
44040
|
+
function customValueStatusOf(planValues, accountValues, now = () => /* @__PURE__ */ new Date()) {
|
|
44041
|
+
const readAt = now().toISOString();
|
|
44042
|
+
if (!accountValues || planValues.length > 0 && accountValues.length === 0) return { filled: [], empty: [], unreadable: true, readAt };
|
|
44043
|
+
const byKey = new Map(accountValues.map((v) => [customValueKeyOf(v.name), v]));
|
|
44044
|
+
const filled2 = [];
|
|
44045
|
+
const empty = [];
|
|
44046
|
+
const unknown = [];
|
|
44047
|
+
for (const cv of planValues) {
|
|
44048
|
+
const live = byKey.get(customValueKeyOf(cv.name));
|
|
44049
|
+
if (!live) empty.push(cv.name);
|
|
44050
|
+
else if (typeof live.value !== "string") unknown.push(cv.name);
|
|
44051
|
+
else if (live.value.trim()) filled2.push(cv.name);
|
|
44052
|
+
else empty.push(cv.name);
|
|
44053
|
+
}
|
|
44054
|
+
return { filled: filled2, empty, ...unknown.length ? { unknown } : {}, readAt };
|
|
44055
|
+
}
|
|
44056
|
+
function handoffCustomValueNote(handoffRef, planValues, status) {
|
|
44057
|
+
if (!status || status.unreadable) return "";
|
|
44058
|
+
const covered = planValues.filter((cv) => cv.filledBy === handoffRef).map((cv) => cv.name);
|
|
44059
|
+
if (!covered.length) return "";
|
|
44060
|
+
const filled2 = covered.filter((n) => status.filled.includes(n));
|
|
44061
|
+
const empty = covered.filter((n) => status.empty.includes(n));
|
|
44062
|
+
const list4 = (xs) => xs.length <= 1 ? xs.join("") : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
44063
|
+
const parts = [
|
|
44064
|
+
filled2.length ? `${list4(filled2)} ${filled2.length === 1 ? "is" : "are"} filled` : "",
|
|
44065
|
+
empty.length ? `${list4(empty)} ${empty.length === 1 ? "is" : "are"} empty` : ""
|
|
44066
|
+
].filter(Boolean);
|
|
44067
|
+
return parts.length ? ` Read back from the account after this build: ${parts.join("; ")}.` : "";
|
|
44068
|
+
}
|
|
44069
|
+
function pieces(text3) {
|
|
44070
|
+
const guarded2 = text3.replace(/\bDr\./g, "Dr\0");
|
|
44071
|
+
const out = [];
|
|
44072
|
+
const re = /([\s\S]*?(?:[.!?](?=\s|$)|(?=\n)|$))(\s*)/g;
|
|
44073
|
+
let m;
|
|
44074
|
+
while ((m = re.exec(guarded2)) && m[0].length) out.push({ sentence: m[1].replace(/Dr\u0000/g, "Dr."), sep: m[2] });
|
|
44075
|
+
return out;
|
|
44076
|
+
}
|
|
44077
|
+
function rejoin(parts) {
|
|
44078
|
+
const kept = parts.filter((p) => p !== null && p.sentence.trim() !== "");
|
|
44079
|
+
return kept.map((p, i) => p.sentence + (i < kept.length - 1 ? p.sep : "")).join("");
|
|
44080
|
+
}
|
|
44081
|
+
function claimsVerifiedPersonNotReal(sentence, verifiedStaff) {
|
|
44082
|
+
for (const raw of verifiedStaff) {
|
|
44083
|
+
const name2 = raw.trim();
|
|
44084
|
+
if (!name2) continue;
|
|
44085
|
+
const n = esc(name2);
|
|
44086
|
+
const after = new RegExp(String.raw`\b${n}\b(?:,\s*|\s+(?:is|was|looks like|appears to be|seems to be|may be|might be)\s+|\s+(?:isn'?t|wasn'?t)\s+)(?:only\s+|just\s+|still\s+|probably\s+)?(?:(?:a|an|the)\s+)?${NOT_REAL_PHRASE}`, "i");
|
|
44087
|
+
const before = new RegExp(String.raw`\b(?:placeholder|fake|dummy|test|sample|demo)(?: (?:user|staff member|person|provider|name))?,?\s+(?:named\s+|called\s+)?${n}\b`, "i");
|
|
44088
|
+
const isnt = new RegExp(String.raw`\b${n}\b\s+(?:isn'?t|is not|wasn'?t|was not)\s+(?:(?:a|an)\s+)?(?:real|actual|genuine)\b`, "i");
|
|
44089
|
+
if (after.test(sentence) || isnt.test(sentence) || before.test(sentence)) return true;
|
|
44090
|
+
}
|
|
44091
|
+
return false;
|
|
44092
|
+
}
|
|
44093
|
+
function mentionPattern(key2) {
|
|
44094
|
+
const words2 = customValueKeyOf(key2).split("_").map(esc);
|
|
44095
|
+
return new RegExp(`\\b${words2.join("[\\s_]+")}\\b`, "gi");
|
|
44096
|
+
}
|
|
44097
|
+
function correctClause(clause, status) {
|
|
44098
|
+
const pred = new RegExp(EMPTY_PRED, "i").exec(clause);
|
|
44099
|
+
if (!pred) return { text: clause, changed: false };
|
|
44100
|
+
const head4 = clause.slice(0, pred.index);
|
|
44101
|
+
const keys = [...status.filled, ...status.empty, ...status.unknown ?? []];
|
|
44102
|
+
const found = [];
|
|
44103
|
+
for (const key2 of keys) for (const m of head4.matchAll(mentionPattern(key2))) found.push({ key: key2, start: m.index, end: m.index + m[0].length });
|
|
44104
|
+
if (!found.length) return { text: clause, changed: false };
|
|
44105
|
+
found.sort((a, b) => a.start - b.start);
|
|
44106
|
+
const last = found[found.length - 1];
|
|
44107
|
+
const tail = head4.slice(last.end);
|
|
44108
|
+
if (!/^\s*(?:custom values?\s*)?(?:\([^)]*\)\s*)?$/i.test(tail)) return { text: clause, changed: false };
|
|
44109
|
+
for (let i = 1; i < found.length; i++) if (!/^\s*(?:,|and|,\s*and|&)\s*$/i.test(head4.slice(found[i - 1].end, found[i].start))) return { text: clause, changed: false };
|
|
44110
|
+
const filledNamed = found.filter((f) => status.filled.includes(f.key));
|
|
44111
|
+
if (!filledNamed.length) return { text: clause, changed: false };
|
|
44112
|
+
const stillEmpty = found.filter((f) => status.empty.includes(f.key));
|
|
44113
|
+
if (!stillEmpty.length) return { text: "", changed: true };
|
|
44114
|
+
const listed = listOf(stillEmpty.map((f) => head4.slice(f.start, f.end)));
|
|
44115
|
+
let out = clause.slice(0, found[0].start) + listed + clause.slice(last.end);
|
|
44116
|
+
if (stillEmpty.length === 1 && found.length > 1) {
|
|
44117
|
+
return { text: out.replace(/\bcustom values\b/i, "custom value").replace(/\b(are|were)(?=\s+(?:still\s+)?(?:left\s+)?(?:empty|blank|unfilled|not|missing)\b)/i, (v) => v.toLowerCase() === "are" ? "is" : "was"), changed: true };
|
|
44118
|
+
}
|
|
44119
|
+
return { text: out, changed: true };
|
|
44120
|
+
}
|
|
44121
|
+
function maskAndNames(clause, keys) {
|
|
44122
|
+
const masked = [];
|
|
44123
|
+
let text3 = clause;
|
|
44124
|
+
for (const key2 of keys.filter((k) => /(?:^|_)and(?:_|$)/.test(customValueKeyOf(k)))) {
|
|
44125
|
+
text3 = text3.replace(mentionPattern(key2), (m) => {
|
|
44126
|
+
masked.push(m);
|
|
44127
|
+
return `${masked.length - 1}`;
|
|
44128
|
+
});
|
|
44129
|
+
}
|
|
44130
|
+
return { text: text3, unmask: (t) => t.replace(/\u0001(\d+)\u0001/g, (_m, i) => masked[Number(i)]) };
|
|
44131
|
+
}
|
|
44132
|
+
function splitAndClaims(clause, keys = []) {
|
|
44133
|
+
const { text: text3, unmask } = maskAndNames(clause, keys);
|
|
44134
|
+
return splitMasked(text3).map(unmask);
|
|
44135
|
+
}
|
|
44136
|
+
function claimsWithin(clause, keys) {
|
|
44137
|
+
const { text: text3, unmask } = maskAndNames(clause, keys);
|
|
44138
|
+
const claims = (t) => new RegExp(EMPTY_PRED, "i").test(t);
|
|
44139
|
+
const bits = text3.split(/(,?\s+and\s+)/i);
|
|
44140
|
+
let from = claims(bits[0]) ? 0 : -1;
|
|
44141
|
+
for (let i = 2; from < 0 && i < bits.length; i += 2) if (claims(bits.slice(i).join(""))) from = i;
|
|
44142
|
+
if (from < 0) return [];
|
|
44143
|
+
return splitMasked(bits.slice(from).join("")).map(unmask).filter((_, i) => i % 2 === 0).filter(claims);
|
|
44144
|
+
}
|
|
44145
|
+
function splitMasked(clause) {
|
|
44146
|
+
const bits = clause.split(/(,?\s+and\s+)/i);
|
|
44147
|
+
const out = [bits[0]];
|
|
44148
|
+
const claims = (t) => new RegExp(EMPTY_PRED, "i").test(t);
|
|
44149
|
+
for (let i = 1; i < bits.length; i += 2) {
|
|
44150
|
+
const joiner = bits[i], next = bits[i + 1] ?? "";
|
|
44151
|
+
if (claims(out[out.length - 1]) && claims(next)) out.push(joiner, next);
|
|
44152
|
+
else out[out.length - 1] += joiner + next;
|
|
44153
|
+
}
|
|
44154
|
+
return out;
|
|
44155
|
+
}
|
|
44156
|
+
function correctEmptinessClaim(sentence, status) {
|
|
44157
|
+
if (!status || status.unreadable) return { text: sentence, changed: false };
|
|
44158
|
+
const keys = [...status.filled, ...status.empty, ...status.unknown ?? []];
|
|
44159
|
+
const parts = [];
|
|
44160
|
+
const outer = sentence.split(CLAUSE_SPLIT);
|
|
44161
|
+
for (let i = 0; i < outer.length; i += 2) {
|
|
44162
|
+
parts.push(...splitAndClaims(outer[i], keys));
|
|
44163
|
+
if (i + 1 < outer.length) parts.push(outer[i + 1]);
|
|
44164
|
+
}
|
|
44165
|
+
let changed = false;
|
|
44166
|
+
const kept = [];
|
|
44167
|
+
let dropNextConsequence = false;
|
|
44168
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
44169
|
+
const joiner = parts[i - 1] ?? "";
|
|
44170
|
+
if (dropNextConsequence && /^\s*,?\s*(?:and so|so)\s+$/i.test(joiner)) {
|
|
44171
|
+
changed = true;
|
|
44172
|
+
for (const piece of claimsWithin(parts[i], keys)) {
|
|
44173
|
+
const c2 = correctClause(piece, status);
|
|
44174
|
+
if (c2.text.trim()) kept.push({ text: c2.text, joiner: ", and " });
|
|
44175
|
+
}
|
|
44176
|
+
continue;
|
|
44177
|
+
}
|
|
44178
|
+
dropNextConsequence = false;
|
|
44179
|
+
const c = correctClause(parts[i], status);
|
|
44180
|
+
if (c.changed) changed = true;
|
|
44181
|
+
if (c.text.trim()) kept.push({ text: c.text, joiner });
|
|
44182
|
+
else dropNextConsequence = true;
|
|
44183
|
+
}
|
|
44184
|
+
if (!changed) return { text: sentence, changed: false };
|
|
44185
|
+
let text3 = kept.map((k, i) => (i ? k.joiner : "") + k.text).join("").trim();
|
|
44186
|
+
if (text3 && !/[.!?]$/.test(text3)) text3 += ".";
|
|
44187
|
+
text3 = text3.charAt(0).toUpperCase() + text3.slice(1);
|
|
44188
|
+
return { text: /^[.!?]?$/.test(text3) ? "" : text3, changed: true };
|
|
44189
|
+
}
|
|
44190
|
+
function guardBuildReport(report3, facts) {
|
|
44191
|
+
const staff = (facts.verifiedStaff ?? []).filter((n) => n.trim());
|
|
44192
|
+
const others = (facts.unverifiedStaff ?? []).filter((n) => n.trim());
|
|
44193
|
+
const fix = (text3) => {
|
|
44194
|
+
let staffClaim = false, changed = false;
|
|
44195
|
+
const parts = pieces(text3).map((p) => {
|
|
44196
|
+
if (staff.length && claimsVerifiedPersonNotReal(p.sentence, staff)) {
|
|
44197
|
+
staffClaim = true;
|
|
44198
|
+
changed = true;
|
|
44199
|
+
return null;
|
|
44200
|
+
}
|
|
44201
|
+
const bullet3 = /^(\s*(?:[-*•]|\d+[.)])\s+)/.exec(p.sentence)?.[1] ?? "";
|
|
44202
|
+
const c = correctEmptinessClaim(p.sentence.slice(bullet3.length), facts.customValues);
|
|
44203
|
+
if (!c.changed) return p;
|
|
44204
|
+
changed = true;
|
|
44205
|
+
return c.text ? { sentence: bullet3 + c.text, sep: p.sep } : null;
|
|
44206
|
+
});
|
|
44207
|
+
return { text: changed ? rejoin(parts) : text3, staffClaim };
|
|
44208
|
+
};
|
|
44209
|
+
const namesOther = (text3) => others.some((n) => new RegExp(`\\b${esc(n)}\\b`, "i").test(text3) || new RegExp(`\\b${esc(n.split(/\s+/)[0])}\\b`).test(text3));
|
|
44210
|
+
const out = { ...report3 };
|
|
44211
|
+
if (typeof report3.summary === "string") out.summary = fix(report3.summary).text;
|
|
44212
|
+
if (Array.isArray(report3.issues)) {
|
|
44213
|
+
out.issues = report3.issues.map((x) => ({ original: x, ...fix(x) })).filter((r) => r.text.trim() && !(r.staffClaim && !namesOther(r.original))).map((r) => r.text);
|
|
44214
|
+
}
|
|
44215
|
+
return out;
|
|
44216
|
+
}
|
|
44217
|
+
var esc, listOf, NOT_REAL_PHRASE, EMPTY_PRED, CLAUSE_SPLIT;
|
|
44218
|
+
var init_build_facts = __esm({
|
|
44219
|
+
"src/build-facts.ts"() {
|
|
44220
|
+
"use strict";
|
|
44221
|
+
esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44222
|
+
listOf = (xs) => xs.length <= 1 ? xs.join("") : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
44223
|
+
NOT_REAL_PHRASE = String.raw`(?:placeholders?|not (?:a |an )?(?:real|actual|genuine)\b|fake|dummy|made[- ]up|stand-?ins?|(?:test|sample|example|demo) (?:users?|accounts?|staff(?: members?)?|persons?|people|providers?|members?|names?|entr(?:y|ies)))`;
|
|
44224
|
+
EMPTY_PRED = String.raw`\b(?:is|are|was|were|remains?|stays?)\s+(?:still\s+)?(?:left\s+)?(?:empty|blank|unfilled|not (?:yet )?(?:been )?(?:filled(?: in)?|set|entered)|missing)\b(?!\s+(?:its|their|the|a|an|from|in the)\b)`;
|
|
44225
|
+
CLAUSE_SPLIT = /(;\s*|,\s*(?:but|while|although|though|so|and so|because|whereas)\s+|\s+but\s+)/i;
|
|
44226
|
+
}
|
|
44227
|
+
});
|
|
44228
|
+
|
|
44229
|
+
// src/build-confirm.ts
|
|
44230
|
+
function previewFingerprint(p) {
|
|
44231
|
+
return contentHash2({
|
|
44232
|
+
locationId: p.locationId,
|
|
44233
|
+
planHash: p.planHash,
|
|
44234
|
+
reuse: p.reuse.map((e) => e.key).sort(),
|
|
44235
|
+
change: p.change.map((e) => [e.key, (e.changes ?? []).map(stableChange).sort(), e.state ?? ""]).sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
44236
|
+
// 3.90.1 (Codex on e4734bc4): the logins the confirmation names are part of what was confirmed.
|
|
44237
|
+
logins: (p.logins ?? []).map((l) => `${l.email.trim().toLowerCase()}|${l.name}|${l.role}`).sort()
|
|
44238
|
+
});
|
|
44239
|
+
}
|
|
44240
|
+
function previewNamesLogins(preview) {
|
|
44241
|
+
return Boolean(preview && Array.isArray(preview.logins));
|
|
44242
|
+
}
|
|
44243
|
+
function composePreview(input) {
|
|
44244
|
+
const reuse = [];
|
|
44245
|
+
const change = [];
|
|
44246
|
+
const createByType = {};
|
|
44247
|
+
const createNames = {};
|
|
44248
|
+
let createCount = 0;
|
|
44249
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44250
|
+
for (const item of input.items) {
|
|
44251
|
+
if (item.status !== "existing" || !item.existingId) {
|
|
44252
|
+
if (item.status === "would_create") {
|
|
44253
|
+
createCount++;
|
|
44254
|
+
createByType[item.type] = (createByType[item.type] ?? 0) + 1;
|
|
44255
|
+
const filled2 = input.bookingCalendar && item.type === "customValues" && customValueKeyOf(item.name) === "booking_link";
|
|
44256
|
+
if (item.name) (createNames[item.type] ??= []).push(filled2 ? `${item.name}, filled with the ${input.bookingCalendar} booking page` : item.name);
|
|
44257
|
+
}
|
|
44258
|
+
continue;
|
|
44259
|
+
}
|
|
44260
|
+
if (input.ours.has(item.existingId)) continue;
|
|
44261
|
+
const key2 = keyOf3(item.type, item.existingId);
|
|
44262
|
+
if (seen.has(key2)) continue;
|
|
44263
|
+
seen.add(key2);
|
|
44264
|
+
const changes = item.type === "workflows" ? input.workflowChanges.get(item.existingId) ?? [] : [];
|
|
44265
|
+
const state2 = input.workflowStates?.get(item.existingId);
|
|
44266
|
+
if (changes.length) change.push({ key: key2, type: item.type, name: item.name, id: item.existingId, changes: [...changes], ...state2 ? { state: state2 } : {} });
|
|
44267
|
+
else reuse.push({ key: key2, type: item.type, name: item.name, id: item.existingId });
|
|
44268
|
+
}
|
|
44269
|
+
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] });
|
|
44270
|
+
const earlierBuild = [];
|
|
44271
|
+
const earlierSeen = /* @__PURE__ */ new Set();
|
|
44272
|
+
for (const item of input.items) {
|
|
44273
|
+
const hit = item.status === "existing" && item.existingId ? input.otherPlans?.get(item.existingId) : void 0;
|
|
44274
|
+
if (hit && !earlierSeen.has(item.existingId)) {
|
|
44275
|
+
earlierSeen.add(item.existingId);
|
|
44276
|
+
earlierBuild.push({ type: item.type, name: item.name || hit.name, runId: hit.runId });
|
|
44277
|
+
}
|
|
44278
|
+
}
|
|
44279
|
+
const unrecordedDrafts = [...reuse, ...change].filter((e) => e.type === "workflows" && input.workflowStatus?.get(e.id) === "draft").map((e) => e.name);
|
|
44280
|
+
const core = { locationId: input.locationId, planHash: input.planHash, reuse, change, logins: [...input.logins ?? []] };
|
|
44281
|
+
return {
|
|
44282
|
+
v: 1,
|
|
44283
|
+
recordKey: input.recordKey,
|
|
44284
|
+
at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
44285
|
+
createCount,
|
|
44286
|
+
createByType,
|
|
44287
|
+
createNames,
|
|
44288
|
+
...core,
|
|
44289
|
+
...earlierBuild.length ? { earlierBuild } : {},
|
|
44290
|
+
...unrecordedDrafts.length ? { unrecordedDrafts } : {},
|
|
44291
|
+
fingerprint: previewFingerprint(core)
|
|
44292
|
+
};
|
|
44293
|
+
}
|
|
44294
|
+
function allowedWorkflowIds(allow) {
|
|
44295
|
+
return new Set(allow.filter((k) => k.startsWith("workflows:")).map((k) => k.slice("workflows:".length)));
|
|
44296
|
+
}
|
|
44297
|
+
function sharedPushAllowed(preview, confirm2, journaledBuild) {
|
|
44298
|
+
const confirmed = preview ? confirm2?.fingerprint === preview.fingerprint : journaledBuild;
|
|
44299
|
+
return confirmed && boardAllowed(preview, confirm2);
|
|
44300
|
+
}
|
|
44301
|
+
function approvedBoardId(preview, confirm2) {
|
|
44302
|
+
const board = preview?.change.find((e) => e.key.startsWith(BOARD_KEY_PREFIX));
|
|
44303
|
+
return board && confirm2 && confirm2.fingerprint === preview.fingerprint && confirm2.allow.includes(board.key) ? board.id : void 0;
|
|
44304
|
+
}
|
|
44305
|
+
function boardAllowed(preview, confirm2) {
|
|
44306
|
+
const board = preview?.change.find((e) => e.key.startsWith(BOARD_KEY_PREFIX));
|
|
44307
|
+
if (!board) return true;
|
|
44308
|
+
return Boolean(confirm2 && confirm2.fingerprint === preview.fingerprint && confirm2.allow.includes(board.key));
|
|
44309
|
+
}
|
|
44310
|
+
function confirmMatches(confirm2, preview) {
|
|
44311
|
+
if (!previewNamesLogins(preview)) return false;
|
|
44312
|
+
if (preview.earlierBuild?.length) return false;
|
|
44313
|
+
if (!confirm2 || confirm2.fingerprint !== preview.fingerprint || !Array.isArray(confirm2.allow)) return false;
|
|
44314
|
+
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
44315
|
+
return confirm2.allow.every((k) => changeKeys.has(k));
|
|
44316
|
+
}
|
|
44317
|
+
function previewsPath(base) {
|
|
44318
|
+
return (0, import_node_path6.join)(base, "build-previews.json");
|
|
44319
|
+
}
|
|
44320
|
+
function readPreviews(base) {
|
|
44321
|
+
try {
|
|
44322
|
+
const raw = JSON.parse((0, import_node_fs7.readFileSync)(previewsPath(base), "utf8"));
|
|
44323
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
44324
|
+
} catch {
|
|
44325
|
+
return {};
|
|
44326
|
+
}
|
|
44327
|
+
}
|
|
44328
|
+
function saveBuildPreview(preview, base = appDataDir()) {
|
|
44329
|
+
(0, import_node_fs7.mkdirSync)(base, { recursive: true, mode: 448 });
|
|
44330
|
+
const all = readPreviews(base);
|
|
44331
|
+
all[preview.recordKey] = preview;
|
|
44332
|
+
const file3 = previewsPath(base);
|
|
44333
|
+
const tmp = `${file3}.${process.pid}.tmp`;
|
|
44334
|
+
(0, import_node_fs7.writeFileSync)(tmp, JSON.stringify(all, null, 2), { mode: 384 });
|
|
44335
|
+
(0, import_node_fs7.renameSync)(tmp, file3);
|
|
44336
|
+
}
|
|
44337
|
+
function readBuildPreview(recordKey, base = appDataDir()) {
|
|
44338
|
+
const p = readPreviews(base)[recordKey];
|
|
44339
|
+
return p && p.v === 1 && typeof p.fingerprint === "string" && Array.isArray(p.reuse) && Array.isArray(p.change) ? p : void 0;
|
|
44340
|
+
}
|
|
44341
|
+
function buildPausedFor(recordKey, confirms, base = appDataDir(), opts = {}) {
|
|
44342
|
+
const preview = readBuildPreview(recordKey, base);
|
|
44343
|
+
if (!preview || confirms?.[recordKey]?.fingerprint === preview.fingerprint) return false;
|
|
44344
|
+
if (opts.since && !(preview.at >= opts.since)) return false;
|
|
44345
|
+
if (opts.lastBuild && !opts.lastBuild.paused && opts.lastBuild.ok === false && opts.lastBuild.at && opts.lastBuild.at > preview.at) return false;
|
|
44346
|
+
return true;
|
|
44347
|
+
}
|
|
44348
|
+
function readBuildConfirm(recordKey, base = appDataDir()) {
|
|
44349
|
+
try {
|
|
44350
|
+
const c = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(base, "cockpit-state.json"), "utf8"))?.buildConfirms?.[recordKey];
|
|
44351
|
+
if (!c || typeof c !== "object") return void 0;
|
|
44352
|
+
const fingerprint2 = Reflect.get(c, "fingerprint");
|
|
44353
|
+
const allow = Reflect.get(c, "allow");
|
|
44354
|
+
const at = Reflect.get(c, "at");
|
|
44355
|
+
if (typeof fingerprint2 !== "string" || !Array.isArray(allow) || typeof at !== "string") return void 0;
|
|
44356
|
+
const by = Reflect.get(c, "by");
|
|
44357
|
+
const rawBefore = Reflect.get(c, "before");
|
|
44358
|
+
const before = rawBefore && typeof rawBefore === "object" ? Object.fromEntries(Object.entries(rawBefore).filter((e) => typeof e[1] === "string")) : {};
|
|
44359
|
+
return { fingerprint: fingerprint2, allow: allow.filter((k) => typeof k === "string"), at, ...typeof by === "string" ? { by } : {}, ...Object.keys(before).length ? { before } : {} };
|
|
44360
|
+
} catch {
|
|
44361
|
+
return void 0;
|
|
44362
|
+
}
|
|
44363
|
+
}
|
|
44364
|
+
function readBoardCreated(locationId2, base = appDataDir()) {
|
|
44365
|
+
try {
|
|
44366
|
+
const id2 = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(base, "cockpit-state.json"), "utf8"))?.sharedBoardCreated?.[locationId2];
|
|
44367
|
+
return typeof id2 === "string" && id2 ? id2 : void 0;
|
|
44368
|
+
} catch {
|
|
44369
|
+
return void 0;
|
|
44370
|
+
}
|
|
44371
|
+
}
|
|
44372
|
+
function allowedChangesFrom(preview, confirm2) {
|
|
44373
|
+
return preview.change.filter((e) => confirm2.allow.includes(e.key)).map((e) => {
|
|
44374
|
+
const b = confirm2.before?.[e.key];
|
|
44375
|
+
return { type: e.type, name: e.name, ghlId: e.id, ...typeof b === "string" ? { before: b } : {} };
|
|
44376
|
+
});
|
|
44377
|
+
}
|
|
44378
|
+
async function readConfirmBefore(preview, allowed, readValue) {
|
|
44379
|
+
const out = {};
|
|
44380
|
+
for (const e of preview.change) {
|
|
44381
|
+
if (!allowed.includes(e.key) || !e.key.startsWith(BOARD_KEY_PREFIX)) continue;
|
|
44382
|
+
const v = await readValue(e.id);
|
|
44383
|
+
if (typeof v !== "string") throw new Error("Command OS could not read the current progress record in GoHighLevel, so it cannot record what an undo would put back. Nothing was confirmed. Try again.");
|
|
44384
|
+
out[e.key] = v;
|
|
44385
|
+
}
|
|
44386
|
+
return out;
|
|
44387
|
+
}
|
|
44388
|
+
function listedValue(raw, id2) {
|
|
44389
|
+
const listed = raw && typeof raw === "object" ? Reflect.get(raw, "customValues") : void 0;
|
|
44390
|
+
const hit = Array.isArray(listed) ? listed.find((v) => !!v && typeof v === "object" && Reflect.get(v, "id") === id2) : void 0;
|
|
44391
|
+
const value = hit && typeof hit === "object" ? Reflect.get(hit, "value") : void 0;
|
|
44392
|
+
return typeof value === "string" ? value : void 0;
|
|
44393
|
+
}
|
|
44394
|
+
function earlierBuildText(earlier, clientName) {
|
|
44395
|
+
const runs = [...new Set(earlier.map((e) => e.runId))];
|
|
44396
|
+
const names = earlier.map((e) => `"${e.name}"`).join(", ");
|
|
44397
|
+
return {
|
|
44398
|
+
why: `${clientName} already has ${earlier.length === 1 ? "an item" : `${earlier.length} items`} an earlier build of a different plan made: ${names}. Building now would build on top of ${earlier.length === 1 ? "it" : "them"}, which is how duplicates happen, so Build will not start. Undo the earlier build first, or stop here.`,
|
|
44399
|
+
undo: runs.map((r) => `Undo build run ${r} for ${clientName}`)
|
|
44400
|
+
};
|
|
44401
|
+
}
|
|
44402
|
+
var import_node_fs7, import_node_path6, BUILD_PAUSED, BUILD_PREVIEW_CHANGED, keyOf3, stableChange, BOARD_VALUE_NAME, BOARD_KEY_PREFIX, BOARD_CHANGE, PREVIEW_KIND;
|
|
44403
|
+
var init_build_confirm = __esm({
|
|
44404
|
+
"src/build-confirm.ts"() {
|
|
44405
|
+
"use strict";
|
|
44406
|
+
import_node_fs7 = require("node:fs");
|
|
44407
|
+
import_node_path6 = require("node:path");
|
|
44408
|
+
init_credentials_store();
|
|
44409
|
+
init_build_contract();
|
|
44410
|
+
init_build_facts();
|
|
44411
|
+
BUILD_PAUSED = "Build paused before writing anything. Open the Build step to see what it will create, what already in the account it will use, and what it would change, then confirm.";
|
|
44412
|
+
BUILD_PREVIEW_CHANGED = "What Build would use or change in the account is not what you confirmed (something in the account or the plan changed). Nothing was written. Look at the Build step again and confirm the new list.";
|
|
44413
|
+
keyOf3 = (type, id2) => `${type}:${id2}`;
|
|
44414
|
+
stableChange = (c) => c.startsWith("could not be read") ? "could not be read" : c;
|
|
44415
|
+
BOARD_VALUE_NAME = "Command OS Board";
|
|
44416
|
+
BOARD_KEY_PREFIX = "board:";
|
|
44417
|
+
BOARD_CHANGE = "the cockpit's own progress record: the cockpit would keep it up to date as this build moves through its steps. Left unticked, the cockpit keeps this client's progress on this computer only";
|
|
44418
|
+
PREVIEW_KIND = {
|
|
44419
|
+
users: "staff member",
|
|
44420
|
+
pipelines: "pipeline",
|
|
44421
|
+
customFields: "custom field",
|
|
44422
|
+
tags: "tag",
|
|
44423
|
+
customValues: "custom value",
|
|
44424
|
+
calendars: "calendar",
|
|
44425
|
+
forms: "form",
|
|
44426
|
+
workflows: "workflow",
|
|
44427
|
+
emails: "email",
|
|
44428
|
+
sms: "text message",
|
|
44429
|
+
emailTemplates: "email template",
|
|
44430
|
+
smsTemplates: "text message template"
|
|
44431
|
+
};
|
|
44432
|
+
}
|
|
44433
|
+
});
|
|
44434
|
+
|
|
43820
44435
|
// src/work-queue.ts
|
|
43821
44436
|
function markKey(locationId2, stageIndex) {
|
|
43822
44437
|
return `${locationId2}:${stageIndex}`;
|
|
@@ -44002,6 +44617,17 @@ function deriveQueue(input) {
|
|
|
44002
44617
|
items.push({ ...base, stage: "Intake", stageIndex: 0, title: "Complete or send the intake", needs: "you", action: "open", href: `/new?client=${encodeURIComponent(locationId2)}`, why: "Brief needs the client's submitted answers. Open this client's intake workspace.", rank: RANK.waitingOnYou });
|
|
44003
44618
|
continue;
|
|
44004
44619
|
}
|
|
44620
|
+
if (next === 3 && buildPausedFor(locationId2, state2.buildConfirms, void 0, { lastBuild: state2.outcomes?.[markKey(locationId2, 3)] })) {
|
|
44621
|
+
items.push({
|
|
44622
|
+
...base,
|
|
44623
|
+
title: `Confirm what Build will do for ${name2}`,
|
|
44624
|
+
needs: "you",
|
|
44625
|
+
action: "open",
|
|
44626
|
+
why: "Build stopped before writing anything. Check its list, then confirm it or stop there.",
|
|
44627
|
+
rank: RANK.waitingOnYou
|
|
44628
|
+
});
|
|
44629
|
+
continue;
|
|
44630
|
+
}
|
|
44005
44631
|
const ownOutcome = state2.outcomes?.[markKey(locationId2, next)];
|
|
44006
44632
|
const ownIssues = ownOutcome?.plain?.issues?.length ? ownOutcome.plain.issues : ownOutcome?.issues ?? [];
|
|
44007
44633
|
if (ownOutcome && ownIssues.length && stages[next] === "pending") {
|
|
@@ -44017,6 +44643,19 @@ function deriveQueue(input) {
|
|
|
44017
44643
|
});
|
|
44018
44644
|
continue;
|
|
44019
44645
|
}
|
|
44646
|
+
if (ownOutcome && ownOutcome.ok === false && !ownIssues.length) {
|
|
44647
|
+
const ownProof = proofFor(state2, locationId2, next);
|
|
44648
|
+
items.push({
|
|
44649
|
+
...base,
|
|
44650
|
+
...ownProof ? { proof: ownProof } : {},
|
|
44651
|
+
title: `${title(next, name2)}: the last run did not finish`,
|
|
44652
|
+
needs: "you",
|
|
44653
|
+
action: "open",
|
|
44654
|
+
why: "The last run stopped before it finished. Open it to see why, then run the step again.",
|
|
44655
|
+
rank: RANK.waitingOnYou
|
|
44656
|
+
});
|
|
44657
|
+
continue;
|
|
44658
|
+
}
|
|
44020
44659
|
if (isHumanStep(next)) {
|
|
44021
44660
|
const handRefusal = next === 5 ? manualSignOffRefusal(state2.manualTasks?.[locationId2] ?? [], verifyRunCompleted(verifyShape(state2.outcomes?.[`${locationId2}:4`]))) : null;
|
|
44022
44661
|
const opensStep = next === 6 || handRefusal !== null;
|
|
@@ -44094,6 +44733,7 @@ var init_work_queue = __esm({
|
|
|
44094
44733
|
init_plain_outcome();
|
|
44095
44734
|
init_manual_tasks();
|
|
44096
44735
|
init_go_live();
|
|
44736
|
+
init_build_confirm();
|
|
44097
44737
|
QUEUE_STEPS = [
|
|
44098
44738
|
{ label: "Intake", todo: "Set up the intake questions for {client}", why: "Command OS puts the questionnaire into their account." },
|
|
44099
44739
|
{ label: "Brief", todo: "Write the build brief for {client}", why: "Command OS turns their answers into the plan." },
|
|
@@ -45256,7 +45896,7 @@ function remember(id2, readAt) {
|
|
|
45256
45896
|
const times = readTimes();
|
|
45257
45897
|
times[id2] = readAt;
|
|
45258
45898
|
ensureAppDataDir();
|
|
45259
|
-
const tmp = `${timesPath()}.${(0,
|
|
45899
|
+
const tmp = `${timesPath()}.${(0, import_node_crypto18.randomUUID)()}.tmp`;
|
|
45260
45900
|
try {
|
|
45261
45901
|
fs28.writeFileSync(tmp, JSON.stringify(times) + "\n", { mode: 384, flag: "wx" });
|
|
45262
45902
|
fs28.renameSync(tmp, timesPath());
|
|
@@ -45310,14 +45950,14 @@ function money2(rows2) {
|
|
|
45310
45950
|
async function readBriefPanels(deps, only) {
|
|
45311
45951
|
return Promise.all(CONNECTOR_REGISTRY.filter((entry) => (!only || only.includes(entry.id)) && (entry.id !== "google-calendar" || calendarShown())).flatMap((entry) => entry.panel ? [entry.panel(briefPanelDeps(deps))] : []));
|
|
45312
45952
|
}
|
|
45313
|
-
var fs28, path27,
|
|
45953
|
+
var fs28, path27, import_node_crypto18, import_zod76, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, decimalsOf, amountText, stripePanel, slackPanel, zoomPanel, calendarPanel;
|
|
45314
45954
|
var init_brief_panels = __esm({
|
|
45315
45955
|
"src/command-os/connectors/brief-panels.ts"() {
|
|
45316
45956
|
"use strict";
|
|
45317
45957
|
init_brief_deadline();
|
|
45318
45958
|
fs28 = __toESM(require("node:fs"));
|
|
45319
45959
|
path27 = __toESM(require("node:path"));
|
|
45320
|
-
|
|
45960
|
+
import_node_crypto18 = require("node:crypto");
|
|
45321
45961
|
import_zod76 = require("zod");
|
|
45322
45962
|
init_credentials_store();
|
|
45323
45963
|
init_brief_row();
|
|
@@ -45889,7 +46529,7 @@ var require_package = __commonJS({
|
|
|
45889
46529
|
"package.json"(exports2, module2) {
|
|
45890
46530
|
module2.exports = {
|
|
45891
46531
|
name: "@elitedcs/ghl-mcp",
|
|
45892
|
-
version: "3.94.
|
|
46532
|
+
version: "3.94.2",
|
|
45893
46533
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
45894
46534
|
description: "GoHighLevel MCP Server for Claude. 250 tools for CRM, automation and marketing, an account-wide workflow audit, live funnel-capture verification and a workflow builder, across client accounts.",
|
|
45895
46535
|
main: "dist/index.js",
|
|
@@ -46108,16 +46748,16 @@ function cockpitSidebarHtml(active, capability) {
|
|
|
46108
46748
|
${groups}
|
|
46109
46749
|
${forThisClientHtml(capability)}
|
|
46110
46750
|
</nav>
|
|
46111
|
-
<div class="cds-side-foot" title="What you type stays here. What Claude reads for you goes to your own Claude account; a link or a call recording is fetched from its own site or recorder first. No copy comes to us.">Runs on this computer. Version <span class="version">${
|
|
46751
|
+
<div class="cds-side-foot" title="What you type stays here. What Claude reads for you goes to your own Claude account; a link or a call recording is fetched from its own site or recorder first. No copy comes to us.">Runs on this computer. Version <span class="version">${esc2(pkg.version)}</span></div>
|
|
46112
46752
|
</aside>`;
|
|
46113
46753
|
}
|
|
46114
46754
|
function homeCrumb(active, opts) {
|
|
46115
46755
|
const home = sidebarPlan === "pro" ? "Your clients" : "Board";
|
|
46116
46756
|
if (active === "board" && !opts.capability) return `<span aria-current="page">${home}</span>`;
|
|
46117
|
-
return `<a href="/" title="${
|
|
46757
|
+
return `<a href="/" title="${esc2(productName())} home">${home}</a><span aria-hidden="true">/</span>${crumbLabel(active, opts.capability, opts.trail)}`;
|
|
46118
46758
|
}
|
|
46119
46759
|
function crumbLabel(active, capability, trail) {
|
|
46120
|
-
if (trail?.length) return trail.map((t, i) => i === trail.length - 1 ? `<span aria-current="page">${
|
|
46760
|
+
if (trail?.length) return trail.map((t, i) => i === trail.length - 1 ? `<span aria-current="page">${esc2(t)}</span>` : `<span>${esc2(t)}</span><span aria-hidden="true">/</span>`).join("");
|
|
46121
46761
|
const cap2 = capability ? CAPABILITIES.find((c) => c.key === capability) : void 0;
|
|
46122
46762
|
if (cap2) return `<span>For this client</span><span aria-hidden="true">/</span><span aria-current="page">${cap2.label}</span>`;
|
|
46123
46763
|
const row = SIDEBAR.flatMap(([, rows2]) => rows2).find((r) => r.screen === active);
|
|
@@ -46138,7 +46778,7 @@ ${cockpitSidebarHtml(active, opts.capability)}
|
|
|
46138
46778
|
<details class="cds-menu" id="acct-menu">
|
|
46139
46779
|
<summary aria-label="Your settings"><span class="cds-avatar sm" id="acct-initials" aria-hidden="true">You</span></summary>
|
|
46140
46780
|
<div class="cds-menu-panel" role="menu">
|
|
46141
|
-
<div class="cds-menu-head"><span class="name" id="acct-name">This computer</span><span class="meta">${productName()} \xB7 Version ${
|
|
46781
|
+
<div class="cds-menu-head"><span class="name" id="acct-name">This computer</span><span class="meta">${productName()} \xB7 Version ${esc2(pkg.version)}</span></div>
|
|
46142
46782
|
<a class="cds-menu-item" role="menuitem" href="/agency">Your agency</a>
|
|
46143
46783
|
<a class="cds-menu-item" role="menuitem" href="/connections">Connections</a>
|
|
46144
46784
|
<a class="cds-menu-item" role="menuitem" href="${HUB_URL}#do">Members Hub</a>
|
|
@@ -46153,12 +46793,12 @@ function cockpitShellClose() {
|
|
|
46153
46793
|
return `</div></div>`;
|
|
46154
46794
|
}
|
|
46155
46795
|
function cockpitHeadHtml(title2, opts = {}) {
|
|
46156
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${
|
|
46796
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc2(title2)}</title>
|
|
46157
46797
|
<script data-theme-boot>${COCKPIT_THEME_HEAD_JS}</script>
|
|
46158
46798
|
<script data-access-boot>${COCKPIT_ACCESS_JS}</script>
|
|
46159
46799
|
<style>${cockpitTokensCss(opts)}${cockpitChromeCss()}${opts.extraCss ?? ""}</style></head>`;
|
|
46160
46800
|
}
|
|
46161
|
-
var
|
|
46801
|
+
var esc2, HUB_URL, COCKPIT_DATE_JS, COCKPIT_MONEY_JS, TYPE_MIN, TYPE_MAX, TYPE_DEFAULT, TYPE_STORAGE_KEY, THEME_STORAGE_KEY, COCKPIT_THEME_HEAD_JS, DS_UI_PX, NAV, sidebarPlan, SIDEBAR, link, recoveryBanner, COCKPIT_NEW_TAB_JS, COCKPIT_TYPE_JS, RECOVERY_BEFORE, RECOVERY_AFTER, COCKPIT_ACCESS_JS;
|
|
46162
46802
|
var init_cockpit_shell = __esm({
|
|
46163
46803
|
"src/command-os/cockpit-shell.ts"() {
|
|
46164
46804
|
"use strict";
|
|
@@ -46167,7 +46807,7 @@ var init_cockpit_shell = __esm({
|
|
|
46167
46807
|
init_script_json();
|
|
46168
46808
|
init_package_version();
|
|
46169
46809
|
init_report_css_generated();
|
|
46170
|
-
|
|
46810
|
+
esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
46171
46811
|
HUB_URL = "https://os.ghlcommand.com/";
|
|
46172
46812
|
COCKPIT_DATE_JS = `
|
|
46173
46813
|
function cockpitDateParts(value){
|
|
@@ -46240,7 +46880,7 @@ function cockpitMoney(amount,currency){
|
|
|
46240
46880
|
{ label: "Brands", href: "/brands", screen: "brands", title: NAV[3].title }
|
|
46241
46881
|
]]
|
|
46242
46882
|
];
|
|
46243
|
-
link = (label2, href, current, title2, sub = false, badgeId) => `<a class="cds-side-link${sub ? " sub" : ""}" href="${href}"${current ? ' aria-current="page"' : ""}${title2 ? ` title="${
|
|
46883
|
+
link = (label2, href, current, title2, sub = false, badgeId) => `<a class="cds-side-link${sub ? " sub" : ""}" href="${href}"${current ? ' aria-current="page"' : ""}${title2 ? ` title="${esc2(title2)}"` : ""}><span>${label2}</span>${badgeId ? `<span class="cds-side-badge" id="${badgeId}" hidden></span>` : ""}</a>`;
|
|
46244
46884
|
recoveryBanner = () => "";
|
|
46245
46885
|
COCKPIT_NEW_TAB_JS = `(function(){if(window.__cmdNewTab)return;window.__cmdNewTab=true;
|
|
46246
46886
|
var SAY=" (opens in a new tab)";
|
|
@@ -46316,7 +46956,7 @@ function canonicalJson(value) {
|
|
|
46316
46956
|
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
46317
46957
|
}
|
|
46318
46958
|
function fingerprintOf(value) {
|
|
46319
|
-
return (0,
|
|
46959
|
+
return (0, import_node_crypto19.createHash)("sha256").update(canonicalJson(value)).digest("hex");
|
|
46320
46960
|
}
|
|
46321
46961
|
function staleRefusal(what, changed) {
|
|
46322
46962
|
const list4 = changed.length ? changed : [`${what} changed`];
|
|
@@ -46418,11 +47058,11 @@ function diffIntakeAnswers(shown, live, questions = []) {
|
|
|
46418
47058
|
}
|
|
46419
47059
|
return [...names].sort().map((n) => `the answer to "${n}"`);
|
|
46420
47060
|
}
|
|
46421
|
-
var
|
|
47061
|
+
var import_node_crypto19, INTAKE_RECHECKED_SENTENCE;
|
|
46422
47062
|
var init_stale_page = __esm({
|
|
46423
47063
|
"src/stale-page.ts"() {
|
|
46424
47064
|
"use strict";
|
|
46425
|
-
|
|
47065
|
+
import_node_crypto19 = require("node:crypto");
|
|
46426
47066
|
INTAKE_RECHECKED_SENTENCE = "We re-checked the intake form since this page loaded; press again to continue.";
|
|
46427
47067
|
}
|
|
46428
47068
|
});
|
|
@@ -46454,7 +47094,7 @@ function parseHeader(text3) {
|
|
|
46454
47094
|
function untrustedDir(dir, own) {
|
|
46455
47095
|
let st;
|
|
46456
47096
|
try {
|
|
46457
|
-
st =
|
|
47097
|
+
st = import_node_fs8.default.lstatSync(dir);
|
|
46458
47098
|
} catch {
|
|
46459
47099
|
return `${dir} is missing`;
|
|
46460
47100
|
}
|
|
@@ -46465,13 +47105,13 @@ function untrustedDir(dir, own) {
|
|
|
46465
47105
|
return null;
|
|
46466
47106
|
}
|
|
46467
47107
|
function ensurePrivateDirs(file3) {
|
|
46468
|
-
const dirs = [[
|
|
47108
|
+
const dirs = [[import_node_path7.default.dirname(file3), false], [import_node_path7.default.join(import_node_path7.default.dirname(file3), STAGING_DIR_NAME), true]];
|
|
46469
47109
|
for (const [dir, own] of dirs) {
|
|
46470
|
-
if (!
|
|
46471
|
-
|
|
47110
|
+
if (!import_node_fs8.default.existsSync(dir)) {
|
|
47111
|
+
import_node_fs8.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
46472
47112
|
if (process.platform !== "win32") {
|
|
46473
47113
|
try {
|
|
46474
|
-
|
|
47114
|
+
import_node_fs8.default.chmodSync(dir, 448);
|
|
46475
47115
|
} catch {
|
|
46476
47116
|
}
|
|
46477
47117
|
}
|
|
@@ -46482,51 +47122,51 @@ function ensurePrivateDirs(file3) {
|
|
|
46482
47122
|
}
|
|
46483
47123
|
function writePrivateFileAtomic(file3, text3) {
|
|
46484
47124
|
ensurePrivateDirs(file3);
|
|
46485
|
-
const stage =
|
|
47125
|
+
const stage = import_node_path7.default.join(import_node_path7.default.dirname(file3), STAGING_DIR_NAME, import_node_path7.default.basename(file3));
|
|
46486
47126
|
let fd;
|
|
46487
47127
|
try {
|
|
46488
|
-
fd =
|
|
47128
|
+
fd = import_node_fs8.default.openSync(stage, "wx", 384);
|
|
46489
47129
|
} catch (e) {
|
|
46490
47130
|
if (e.code !== "EEXIST") throw e;
|
|
46491
47131
|
let age = 0;
|
|
46492
47132
|
try {
|
|
46493
|
-
age = Date.now() -
|
|
47133
|
+
age = Date.now() - import_node_fs8.default.statSync(stage).mtimeMs;
|
|
46494
47134
|
} catch {
|
|
46495
47135
|
}
|
|
46496
47136
|
if (age < STALE_STAGE_MS) return "busy";
|
|
46497
47137
|
try {
|
|
46498
|
-
|
|
47138
|
+
import_node_fs8.default.unlinkSync(stage);
|
|
46499
47139
|
} catch {
|
|
46500
47140
|
}
|
|
46501
|
-
fd =
|
|
47141
|
+
fd = import_node_fs8.default.openSync(stage, "wx", 384);
|
|
46502
47142
|
}
|
|
46503
47143
|
try {
|
|
46504
47144
|
let opened;
|
|
46505
47145
|
try {
|
|
46506
|
-
|
|
46507
|
-
|
|
46508
|
-
opened =
|
|
47146
|
+
import_node_fs8.default.writeSync(fd, text3);
|
|
47147
|
+
import_node_fs8.default.fsyncSync(fd);
|
|
47148
|
+
opened = import_node_fs8.default.fstatSync(fd);
|
|
46509
47149
|
} finally {
|
|
46510
|
-
|
|
47150
|
+
import_node_fs8.default.closeSync(fd);
|
|
46511
47151
|
}
|
|
46512
|
-
if (!sameFile(opened,
|
|
46513
|
-
|
|
46514
|
-
if (!sameFile(opened,
|
|
47152
|
+
if (!sameFile(opened, import_node_fs8.default.lstatSync(stage))) throw new Error("cache: the staged file was replaced before it was installed");
|
|
47153
|
+
import_node_fs8.default.renameSync(stage, file3);
|
|
47154
|
+
if (!sameFile(opened, import_node_fs8.default.lstatSync(file3))) throw new Error("cache: the installed file is not the one written");
|
|
46515
47155
|
return "written";
|
|
46516
47156
|
} catch (e) {
|
|
46517
47157
|
try {
|
|
46518
|
-
|
|
47158
|
+
import_node_fs8.default.unlinkSync(stage);
|
|
46519
47159
|
} catch {
|
|
46520
47160
|
}
|
|
46521
47161
|
throw e;
|
|
46522
47162
|
}
|
|
46523
47163
|
}
|
|
46524
47164
|
function defaultCacheFile(appDataDir2) {
|
|
46525
|
-
return
|
|
47165
|
+
return import_node_path7.default.join(appDataDir2, CACHE_FILE_NAME);
|
|
46526
47166
|
}
|
|
46527
47167
|
function removeCacheFileAtRest(file3) {
|
|
46528
47168
|
try {
|
|
46529
|
-
|
|
47169
|
+
import_node_fs8.default.unlinkSync(file3);
|
|
46530
47170
|
} catch {
|
|
46531
47171
|
}
|
|
46532
47172
|
}
|
|
@@ -46591,14 +47231,14 @@ function assertCommitted(what) {
|
|
|
46591
47231
|
const r = regions.getStore();
|
|
46592
47232
|
if (r && r.committing === 0) throw new UncommittedWriteError(what);
|
|
46593
47233
|
}
|
|
46594
|
-
var import_node_async_hooks2,
|
|
47234
|
+
var import_node_async_hooks2, import_node_crypto20, import_node_fs8, import_node_path7, STALE_MS, MAX_AGE_MS, RECORD_MAX_BYTES, FILE_MAX_BYTES, SAVE_COALESCE_MS, OTHER_PROCESS_GRACE_MS, FAILED_RETRY_MS, CACHE_FILE_NAME, COLD_TEXT, sha256, STAGING_DIR_NAME, STALE_STAGE_MS, sameFile, CockpitCache, PACE, DEADLINE_TEXT, NEVER, PacerDeadlineError, DAILY_CEILING_TEXT, SLOW_DOWN_TEXT, Pacer, PacerCeilingError, PACED_METHODS, regions, UncommittedWriteError, StaleReadError, CockpitCacheService, AGE_LINE_JS, ageLine;
|
|
46595
47235
|
var init_cockpit_cache = __esm({
|
|
46596
47236
|
"src/cockpit-cache.ts"() {
|
|
46597
47237
|
"use strict";
|
|
46598
47238
|
import_node_async_hooks2 = require("node:async_hooks");
|
|
46599
|
-
|
|
46600
|
-
|
|
46601
|
-
|
|
47239
|
+
import_node_crypto20 = require("node:crypto");
|
|
47240
|
+
import_node_fs8 = __toESM(require("node:fs"));
|
|
47241
|
+
import_node_path7 = __toESM(require("node:path"));
|
|
46602
47242
|
init_stale_page();
|
|
46603
47243
|
STALE_MS = 15 * 6e4;
|
|
46604
47244
|
MAX_AGE_MS = 7 * 24 * 60 * 6e4;
|
|
@@ -46609,7 +47249,7 @@ var init_cockpit_cache = __esm({
|
|
|
46609
47249
|
FAILED_RETRY_MS = 2 * 6e4;
|
|
46610
47250
|
CACHE_FILE_NAME = "cockpit-cache.json";
|
|
46611
47251
|
COLD_TEXT = "Loading from GoHighLevel";
|
|
46612
|
-
sha256 = (s) => (0,
|
|
47252
|
+
sha256 = (s) => (0, import_node_crypto20.createHash)("sha256").update(s).digest("hex");
|
|
46613
47253
|
STAGING_DIR_NAME = ".cockpit-cache-staging";
|
|
46614
47254
|
STALE_STAGE_MS = 3e4;
|
|
46615
47255
|
sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino;
|
|
@@ -46863,7 +47503,7 @@ var init_cockpit_cache = __esm({
|
|
|
46863
47503
|
this.dirty = false;
|
|
46864
47504
|
if (this.deps.file) {
|
|
46865
47505
|
try {
|
|
46866
|
-
|
|
47506
|
+
import_node_fs8.default.unlinkSync(this.deps.file);
|
|
46867
47507
|
} catch {
|
|
46868
47508
|
}
|
|
46869
47509
|
}
|
|
@@ -46891,7 +47531,7 @@ var init_cockpit_cache = __esm({
|
|
|
46891
47531
|
}
|
|
46892
47532
|
let text3;
|
|
46893
47533
|
try {
|
|
46894
|
-
text3 =
|
|
47534
|
+
text3 = import_node_fs8.default.readFileSync(file3, "utf8");
|
|
46895
47535
|
} catch (e) {
|
|
46896
47536
|
if (e.code !== "ENOENT") this.log(`cache: could not read ${CACHE_FILE_NAME}; starting cold`);
|
|
46897
47537
|
return 0;
|
|
@@ -46900,7 +47540,7 @@ var init_cockpit_cache = __esm({
|
|
|
46900
47540
|
if (!h) {
|
|
46901
47541
|
if (!this.otherProcessRecent(file3)) {
|
|
46902
47542
|
try {
|
|
46903
|
-
|
|
47543
|
+
import_node_fs8.default.unlinkSync(file3);
|
|
46904
47544
|
} catch {
|
|
46905
47545
|
}
|
|
46906
47546
|
}
|
|
@@ -46931,7 +47571,7 @@ var init_cockpit_cache = __esm({
|
|
|
46931
47571
|
let h = header;
|
|
46932
47572
|
if (h === void 0) {
|
|
46933
47573
|
try {
|
|
46934
|
-
h = parseHeader(
|
|
47574
|
+
h = parseHeader(import_node_fs8.default.readFileSync(file3, "utf8"));
|
|
46935
47575
|
} catch {
|
|
46936
47576
|
h = null;
|
|
46937
47577
|
}
|
|
@@ -46939,7 +47579,7 @@ var init_cockpit_cache = __esm({
|
|
|
46939
47579
|
if (h && h.pid !== this.pid && this.now() - Date.parse(h.savedAt) < OTHER_PROCESS_GRACE_MS) return true;
|
|
46940
47580
|
if (!h) {
|
|
46941
47581
|
try {
|
|
46942
|
-
const st =
|
|
47582
|
+
const st = import_node_fs8.default.statSync(file3);
|
|
46943
47583
|
return this.now() - st.mtimeMs < OTHER_PROCESS_GRACE_MS;
|
|
46944
47584
|
} catch {
|
|
46945
47585
|
return false;
|
|
@@ -46982,7 +47622,7 @@ var init_cockpit_cache = __esm({
|
|
|
46982
47622
|
}
|
|
46983
47623
|
let disk = null;
|
|
46984
47624
|
try {
|
|
46985
|
-
disk = parseHeader(
|
|
47625
|
+
disk = parseHeader(import_node_fs8.default.readFileSync(file3, "utf8"));
|
|
46986
47626
|
} catch {
|
|
46987
47627
|
disk = null;
|
|
46988
47628
|
}
|
|
@@ -48054,42 +48694,42 @@ function heldWaits(rows2) {
|
|
|
48054
48694
|
return out;
|
|
48055
48695
|
}
|
|
48056
48696
|
function shopperDir(base = appDataDir()) {
|
|
48057
|
-
return
|
|
48697
|
+
return import_node_path8.default.join(base, "secret-shopper");
|
|
48058
48698
|
}
|
|
48059
48699
|
function runPath(locationId2, runId, base) {
|
|
48060
48700
|
if (!SAFE_ID.test(locationId2) || !SAFE_ID.test(runId)) throw new Error("That client or run id is not valid.");
|
|
48061
|
-
return
|
|
48701
|
+
return import_node_path8.default.join(shopperDir(base), locationId2, `${runId}.json`);
|
|
48062
48702
|
}
|
|
48063
48703
|
function otherBusinessNames(locations, self) {
|
|
48064
48704
|
return locations.filter((l) => l.locationId !== self && typeof l.name === "string" && l.name.trim() !== "").map((l) => l.name.trim());
|
|
48065
48705
|
}
|
|
48066
48706
|
function saveRun(run, base) {
|
|
48067
48707
|
const file3 = runPath(run.locationId, run.runId, base);
|
|
48068
|
-
|
|
48708
|
+
import_node_fs9.default.mkdirSync(import_node_path8.default.dirname(file3), { recursive: true, mode: 448 });
|
|
48069
48709
|
const tmp = `${file3}.${process.pid}.${Date.now()}.tmp`;
|
|
48070
|
-
|
|
48071
|
-
|
|
48710
|
+
import_node_fs9.default.writeFileSync(tmp, JSON.stringify(run, null, 1), { mode: 384 });
|
|
48711
|
+
import_node_fs9.default.renameSync(tmp, file3);
|
|
48072
48712
|
}
|
|
48073
48713
|
function readRun(locationId2, runId, base) {
|
|
48074
48714
|
try {
|
|
48075
|
-
return JSON.parse(
|
|
48715
|
+
return JSON.parse(import_node_fs9.default.readFileSync(runPath(locationId2, runId, base), "utf8"));
|
|
48076
48716
|
} catch {
|
|
48077
48717
|
return void 0;
|
|
48078
48718
|
}
|
|
48079
48719
|
}
|
|
48080
48720
|
function listRuns(locationId2, base) {
|
|
48081
48721
|
if (!SAFE_ID.test(locationId2)) return [];
|
|
48082
|
-
const dir =
|
|
48722
|
+
const dir = import_node_path8.default.join(shopperDir(base), locationId2);
|
|
48083
48723
|
let names = [];
|
|
48084
48724
|
try {
|
|
48085
|
-
names =
|
|
48725
|
+
names = import_node_fs9.default.readdirSync(dir).filter((n) => n.endsWith(".json"));
|
|
48086
48726
|
} catch {
|
|
48087
48727
|
return [];
|
|
48088
48728
|
}
|
|
48089
48729
|
const runs = [];
|
|
48090
48730
|
for (const n of names) {
|
|
48091
48731
|
try {
|
|
48092
|
-
runs.push(JSON.parse(
|
|
48732
|
+
runs.push(JSON.parse(import_node_fs9.default.readFileSync(import_node_path8.default.join(dir, n), "utf8")));
|
|
48093
48733
|
} catch {
|
|
48094
48734
|
}
|
|
48095
48735
|
}
|
|
@@ -48170,7 +48810,7 @@ async function findContactIdByEmail(client, locationId2, email) {
|
|
|
48170
48810
|
async function startShopper(input, deps) {
|
|
48171
48811
|
const now = deps.now ?? Date.now;
|
|
48172
48812
|
const sleep8 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
48173
|
-
const token2 =
|
|
48813
|
+
const token2 = import_node_crypto21.default.randomUUID().replace(/-/g, "").slice(0, 8);
|
|
48174
48814
|
const leadEmail = plusAddress(input.ownerEmail, token2);
|
|
48175
48815
|
if (!SAFE_ID.test(input.locationId)) throw new Error("That client id is not valid.");
|
|
48176
48816
|
if (input.entry.kind === "workflows" && input.entry.workflowIds.length === 0) throw new Error("Choose at least one workflow for the test lead to enter.");
|
|
@@ -48373,20 +49013,20 @@ function pidAlive(pid) {
|
|
|
48373
49013
|
}
|
|
48374
49014
|
function claimRun(locationId2, runId, base) {
|
|
48375
49015
|
const lock = `${runPath(locationId2, runId, base)}.lock`;
|
|
48376
|
-
|
|
48377
|
-
const token2 =
|
|
49016
|
+
import_node_fs9.default.mkdirSync(import_node_path8.default.dirname(lock), { recursive: true, mode: 448 });
|
|
49017
|
+
const token2 = import_node_crypto21.default.randomUUID();
|
|
48378
49018
|
const mine = JSON.stringify({ pid: process.pid, token: token2 });
|
|
48379
49019
|
for (let attempt2 = 0; attempt2 < 3; attempt2++) {
|
|
48380
49020
|
try {
|
|
48381
|
-
const fd =
|
|
49021
|
+
const fd = import_node_fs9.default.openSync(lock, "wx", 384);
|
|
48382
49022
|
try {
|
|
48383
|
-
|
|
49023
|
+
import_node_fs9.default.writeSync(fd, mine);
|
|
48384
49024
|
} finally {
|
|
48385
|
-
|
|
49025
|
+
import_node_fs9.default.closeSync(fd);
|
|
48386
49026
|
}
|
|
48387
49027
|
return () => {
|
|
48388
49028
|
try {
|
|
48389
|
-
if (
|
|
49029
|
+
if (import_node_fs9.default.readFileSync(lock, "utf8") === mine) import_node_fs9.default.unlinkSync(lock);
|
|
48390
49030
|
} catch {
|
|
48391
49031
|
}
|
|
48392
49032
|
};
|
|
@@ -48395,8 +49035,8 @@ function claimRun(locationId2, runId, base) {
|
|
|
48395
49035
|
let held2 = "";
|
|
48396
49036
|
let age = 0;
|
|
48397
49037
|
try {
|
|
48398
|
-
held2 =
|
|
48399
|
-
age = Date.now() -
|
|
49038
|
+
held2 = import_node_fs9.default.readFileSync(lock, "utf8");
|
|
49039
|
+
age = Date.now() - import_node_fs9.default.statSync(lock).mtimeMs;
|
|
48400
49040
|
} catch {
|
|
48401
49041
|
continue;
|
|
48402
49042
|
}
|
|
@@ -48408,7 +49048,7 @@ function claimRun(locationId2, runId, base) {
|
|
|
48408
49048
|
const stale = age > 10 * 6e4 || pid > 0 && !pidAlive(pid);
|
|
48409
49049
|
if (!stale) return null;
|
|
48410
49050
|
try {
|
|
48411
|
-
if (
|
|
49051
|
+
if (import_node_fs9.default.readFileSync(lock, "utf8") === held2) import_node_fs9.default.unlinkSync(lock);
|
|
48412
49052
|
} catch {
|
|
48413
49053
|
}
|
|
48414
49054
|
}
|
|
@@ -48507,13 +49147,13 @@ function summarizeRun(run) {
|
|
|
48507
49147
|
if (run.status === "watching") return `So far: ${head4}${stepNote} Still watching until ${new Date(run.closesAt).toISOString()}.`;
|
|
48508
49148
|
return gradeLine + head4 + stepNote;
|
|
48509
49149
|
}
|
|
48510
|
-
var
|
|
49150
|
+
var import_node_fs9, import_node_path8, import_node_crypto21, MAX_EXTEND_MINUTES, asObj8, asArr4, str10, SAFE_ID, notFound;
|
|
48511
49151
|
var init_run = __esm({
|
|
48512
49152
|
"src/command-os/shopper/run.ts"() {
|
|
48513
49153
|
"use strict";
|
|
48514
|
-
|
|
48515
|
-
|
|
48516
|
-
|
|
49154
|
+
import_node_fs9 = __toESM(require("node:fs"));
|
|
49155
|
+
import_node_path8 = __toESM(require("node:path"));
|
|
49156
|
+
import_node_crypto21 = __toESM(require("node:crypto"));
|
|
48517
49157
|
init_credentials_store();
|
|
48518
49158
|
init_analyze();
|
|
48519
49159
|
init_links2();
|
|
@@ -48629,7 +49269,7 @@ function planCopy(src, opts) {
|
|
|
48629
49269
|
const idMap = /* @__PURE__ */ new Map();
|
|
48630
49270
|
for (const n of nodes) {
|
|
48631
49271
|
const id2 = str11(n.id);
|
|
48632
|
-
if (id2) idMap.set(id2,
|
|
49272
|
+
if (id2) idMap.set(id2, import_node_crypto22.default.randomUUID());
|
|
48633
49273
|
}
|
|
48634
49274
|
const remap = (id2) => typeof id2 === "string" ? idMap.get(id2) ?? id2 : id2;
|
|
48635
49275
|
const waits = [];
|
|
@@ -48725,13 +49365,13 @@ function remapWorkflows(value, sourceToCopy, key2) {
|
|
|
48725
49365
|
}
|
|
48726
49366
|
function journal(e, base) {
|
|
48727
49367
|
const f = journalFile(base);
|
|
48728
|
-
|
|
48729
|
-
|
|
49368
|
+
import_node_fs10.default.mkdirSync(import_node_path9.default.dirname(f), { recursive: true, mode: 448 });
|
|
49369
|
+
import_node_fs10.default.appendFileSync(f, JSON.stringify(e) + "\n", { encoding: "utf8", mode: 384 });
|
|
48730
49370
|
}
|
|
48731
49371
|
function openCopies(locationId2, base) {
|
|
48732
49372
|
let lines = [];
|
|
48733
49373
|
try {
|
|
48734
|
-
lines =
|
|
49374
|
+
lines = import_node_fs10.default.readFileSync(journalFile(base), "utf8").split("\n").filter(Boolean);
|
|
48735
49375
|
} catch {
|
|
48736
49376
|
return [];
|
|
48737
49377
|
}
|
|
@@ -48886,13 +49526,13 @@ function builderAdmin(b, check) {
|
|
|
48886
49526
|
remove: (id2, ack) => b.deleteWorkflow(id2, ack ? { acknowledgeContacts: ack } : void 0)
|
|
48887
49527
|
};
|
|
48888
49528
|
}
|
|
48889
|
-
var
|
|
49529
|
+
var import_node_fs10, import_node_path9, import_node_crypto22, asObj9, asArr5, str11, FF_PREFIX, ALLOWED, hostOf, normalizeHost, newToken, TOKEN_NAME, tokenPrefix, DAY_NAMES, remapSelf, journalFile, SWEEP_MIN_AGE_MS;
|
|
48890
49530
|
var init_fastforward = __esm({
|
|
48891
49531
|
"src/command-os/shopper/fastforward.ts"() {
|
|
48892
49532
|
"use strict";
|
|
48893
|
-
|
|
48894
|
-
|
|
48895
|
-
|
|
49533
|
+
import_node_fs10 = __toESM(require("node:fs"));
|
|
49534
|
+
import_node_path9 = __toESM(require("node:path"));
|
|
49535
|
+
import_node_crypto22 = __toESM(require("node:crypto"));
|
|
48896
49536
|
init_run();
|
|
48897
49537
|
init_workflow_publish_gate();
|
|
48898
49538
|
asObj9 = (v) => v && typeof v === "object" ? v : {};
|
|
@@ -48930,14 +49570,14 @@ var init_fastforward = __esm({
|
|
|
48930
49570
|
};
|
|
48931
49571
|
newToken = () => {
|
|
48932
49572
|
let t = "";
|
|
48933
|
-
while (t.length < 6) t += (
|
|
49573
|
+
while (t.length < 6) t += (import_node_crypto22.default.randomBytes(1)[0] % 36).toString(36);
|
|
48934
49574
|
return t;
|
|
48935
49575
|
};
|
|
48936
49576
|
TOKEN_NAME = new RegExp(`^${FF_PREFIX} [0-9a-z]{6} `);
|
|
48937
49577
|
tokenPrefix = (name2) => name2.match(TOKEN_NAME)?.[0] ?? "\0never";
|
|
48938
49578
|
DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
48939
49579
|
remapSelf = (value, sourceId, copyId) => remapWorkflows(value, /* @__PURE__ */ new Map([[sourceId, copyId]]));
|
|
48940
|
-
journalFile = (base) =>
|
|
49580
|
+
journalFile = (base) => import_node_path9.default.join(shopperDir(base), "fast-forward-journal.jsonl");
|
|
48941
49581
|
SWEEP_MIN_AGE_MS = 15 * 6e4;
|
|
48942
49582
|
}
|
|
48943
49583
|
});
|
|
@@ -48950,16 +49590,16 @@ function capped(rows2, wrap) {
|
|
|
48950
49590
|
function ringHtml(score2, caption, complete = true) {
|
|
48951
49591
|
const s = Math.max(0, Math.min(100, Math.round(score2)));
|
|
48952
49592
|
const r = 52, c = 2 * Math.PI * r, g = gradeOf(s), k = complete ? `cds-g-${g.toLowerCase()}` : "cds-g-incomplete";
|
|
48953
|
-
return `<div class="cds-ring" role="img" aria-label="Grade ${g}, ${s} out of 100${complete ? "" : ""}. ${
|
|
49593
|
+
return `<div class="cds-ring" role="img" aria-label="Grade ${g}, ${s} out of 100${complete ? "" : ""}. ${esc3(caption)}"><svg width="160" height="160" viewBox="0 0 120 120" aria-hidden="true"><circle class="trk" cx="60" cy="60" r="${r}" stroke-width="10"></circle><circle class="arc ${k}" cx="60" cy="60" r="${r}" stroke-width="10" stroke-dasharray="${c.toFixed(2)}" stroke-dashoffset="${(c * (1 - s / 100)).toFixed(2)}" transform="rotate(-90 60 60)"></circle><text class="letter ${k}" x="60" y="66" text-anchor="middle" font-size="44">${g}</text><text class="out" x="60" y="86" text-anchor="middle">${s}/100</text></svg><div class="cap">${esc3(caption)}</div></div>`;
|
|
48954
49594
|
}
|
|
48955
49595
|
function stepRow(r) {
|
|
48956
49596
|
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : r.notTested ? ["", "Not tested"] : OUTCOME[r.outcome] ?? ["", ""];
|
|
48957
|
-
return `<li><span class="t"><time data-ms="${r.at}">${
|
|
49597
|
+
return `<li><span class="t"><time data-ms="${r.at}">${esc3(new Date(r.at).toISOString().slice(11, 16))}</time></span>${dot(tone)}<span class="what">${esc3(r.stepName)}<small>${esc3(r.detail)}</small></span>${word ? chip(tone, word) : ""}</li>`;
|
|
48958
49598
|
}
|
|
48959
49599
|
function stepsHtml(a) {
|
|
48960
49600
|
if (!a) return "";
|
|
48961
|
-
if (!a.read) return panel("Every step", "What each workflow step did", "", callout("warn", "",
|
|
48962
|
-
const rows2 = a.problems.map((p) => `<li><span class="ttl">${
|
|
49601
|
+
if (!a.read) return panel("Every step", "What each workflow step did", "", callout("warn", "", esc3(a.unreadable ?? "The step log could not be read.")));
|
|
49602
|
+
const rows2 = a.problems.map((p) => `<li><span class="ttl">${esc3(p.stepName)}<span class="sub">${esc3(p.detail)}</span><span class="sub"><b>The fix:</b> ${esc3(p.fix)}</span></span><span class="end">${/^The Checkup found: /.test(p.detail ?? "") && p.kind !== "failed_holding" && p.kind !== "failed_gave_up" ? chip("bad", "Failed") : chip(p.kind === "skipped" ? "warn" : "bad", PROBLEM_CHIP[p.kind])}${chip("", p.workflowName)}</span></li>`);
|
|
48963
49603
|
const diagnosed = a.problems.filter((p) => p.kind !== "failed_holding" && p.kind !== "failed_gave_up" && /^The Checkup found: /.test(p.detail ?? "")).length;
|
|
48964
49604
|
const byWf = /* @__PURE__ */ new Map();
|
|
48965
49605
|
for (const r of a.rows) byWf.set(r.workflowName, [...byWf.get(r.workflowName) ?? [], r]);
|
|
@@ -48969,39 +49609,39 @@ function stepsHtml(a) {
|
|
|
48969
49609
|
...a.rows.length === 0 ? ["No steps were logged for the test lead. That is not a pass: the lead may not have entered, or GoHighLevel had not logged it yet."] : [],
|
|
48970
49610
|
...a.counts.unclear ? [`${a.counts.unclear} step${a.counts.unclear === 1 ? " has" : "s have"} a status this version cannot read; check ${a.counts.unclear === 1 ? "it" : "them"} in GoHighLevel's Execution logs.`] : []
|
|
48971
49611
|
];
|
|
48972
|
-
const noteHtml = notes.length ? callout("warn", "", notes.map(
|
|
48973
|
-
const lists = [...byWf.entries()].map(([name2, steps]) => `<details${a.problems.length ? " open" : ""}><summary class="cds-link">${
|
|
49612
|
+
const noteHtml = notes.length ? callout("warn", "", notes.map(esc3).join("<br>")) : "";
|
|
49613
|
+
const lists = [...byWf.entries()].map(([name2, steps]) => `<details${a.problems.length ? " open" : ""}><summary class="cds-link">${esc3(name2)} (${steps.length} step${steps.length === 1 ? "" : "s"})</summary>${capped(steps.map(stepRow), tl)}</details>`).join("");
|
|
48974
49614
|
return `${a.problems.length ? panel("Fix these too", "Steps that did not do their job", "From GoHighLevel's own execution log", capped(rows2, list2)) : ""}
|
|
48975
49615
|
${panel("Every step", "What each workflow step did", `${a.counts.ran} ran \xB7 ${Math.max(0, a.counts.didNothing - diagnosed)} skipped or did nothing \xB7 ${a.counts.failed + diagnosed} failed`, noteHtml + lists)}`;
|
|
48976
49616
|
}
|
|
48977
49617
|
function marked(quote2, highlight) {
|
|
48978
|
-
if (!highlight) return
|
|
49618
|
+
if (!highlight) return esc3(quote2);
|
|
48979
49619
|
const i = quote2.indexOf(highlight);
|
|
48980
|
-
if (i < 0) return
|
|
48981
|
-
return `${
|
|
49620
|
+
if (i < 0) return esc3(quote2);
|
|
49621
|
+
return `${esc3(quote2.slice(0, i))}<mark>${esc3(highlight)}</mark>${esc3(quote2.slice(i + highlight.length))}`;
|
|
48982
49622
|
}
|
|
48983
49623
|
function problemRow(p) {
|
|
48984
|
-
return `<li><span class="ttl">${
|
|
49624
|
+
return `<li><span class="ttl">${esc3(p.title)}<span class="sub">What the lead got: "${marked(p.quote, p.highlight)}"</span><span class="sub">Sent by: ${p.sentBy ? esc3(p.sentBy) : "Not matched to a planned step"}</span><span class="sub"><b>The fix:</b> ${esc3(p.fix)}</span></span><span class="end">${chip("bad", CHIP[p.kind])}${chip("info", CHANNEL[p.channel] ?? "Message")}${chip("", mins(p.minute))}</span></li>`;
|
|
48985
49625
|
}
|
|
48986
49626
|
function timelineRow(t) {
|
|
48987
49627
|
const [tone, word] = t.problems.length === 0 ? ["ok", "OK"] : t.problems.length === 1 ? ["bad", CHIP[t.problems[0]]] : ["bad", `${t.problems.length} problems`];
|
|
48988
|
-
const what = t.channel === "email" ? `Email: "${
|
|
48989
|
-
return `<li><span class="t">${mins(t.minute)}</span>${dot(tone)}<span class="what">${what}<small>${
|
|
49628
|
+
const what = t.channel === "email" ? `Email: "${esc3(t.subject ?? t.preview)}"` : `${CHANNEL[t.channel] ?? "Message"}: "${esc3(t.preview)}"`;
|
|
49629
|
+
return `<li><span class="t">${mins(t.minute)}</span>${dot(tone)}<span class="what">${what}<small>${esc3(t.sentBy ?? (t.source === "workflow" ? "Sent by a workflow" : t.source ? `Sent by ${t.source}` : ""))}</small></span>${chip(tone, word)}</li>`;
|
|
48990
49630
|
}
|
|
48991
49631
|
function aboutHtml(run) {
|
|
48992
49632
|
const rows2 = [
|
|
48993
49633
|
["Started", when(run.startedAt)],
|
|
48994
|
-
["Test lead", `${
|
|
49634
|
+
["Test lead", `${esc3(run.leadEmail)} (your address, not a customer's)`],
|
|
48995
49635
|
...run.leadHasFirstName ? [] : [["First name", "No first name, on purpose"]],
|
|
48996
|
-
["Watching for", `${run.watchMinutes} minute${run.watchMinutes === 1 ? "" : "s"}${run.watchReason ? `. ${
|
|
49636
|
+
["Watching for", `${run.watchMinutes} minute${run.watchMinutes === 1 ? "" : "s"}${run.watchReason ? `. ${esc3(run.watchReason)}` : ""}`]
|
|
48997
49637
|
];
|
|
48998
49638
|
return panel("The test lead", "How this check ran", "", `<div class="cds-table-wrap"><table class="cds-table"><tbody>${rows2.map(([k, v]) => `<tr><th scope="row">${k}</th><td>${v}</td></tr>`).join("")}</tbody></table></div>`);
|
|
48999
49639
|
}
|
|
49000
49640
|
function resultHtml(run, now, clientName, action) {
|
|
49001
49641
|
if (run.status === "failed") {
|
|
49002
49642
|
return {
|
|
49003
|
-
head: pageHead("The Workflow Audit Pro could not run",
|
|
49004
|
-
rest: callout("warn", "",
|
|
49643
|
+
head: pageHead("The Workflow Audit Pro could not run", esc3(run.error ?? "Unknown error"), action),
|
|
49644
|
+
rest: callout("warn", "", esc3(run.cleanup?.detail ?? "No test lead was sent."))
|
|
49005
49645
|
};
|
|
49006
49646
|
}
|
|
49007
49647
|
const f = run.findings;
|
|
@@ -49010,8 +49650,8 @@ function resultHtml(run, now, clientName, action) {
|
|
|
49010
49650
|
if (run.status === "done" && card.graded && f) {
|
|
49011
49651
|
const href = reportHref(run);
|
|
49012
49652
|
const charged2 = card.deductions.filter((d) => d.points > 0).length;
|
|
49013
|
-
const leftovers = `${run.fastForward && !run.fastForward.removed ? callout("warn", "Not cleaned up:", ` temporary copies are still in the account: ${
|
|
49014
|
-
const summary = `<div class="sum"><div class="cds-stack"><p>Finished ${when(run.finishedAt ?? run.startedAt)}. ${
|
|
49653
|
+
const leftovers = `${run.fastForward && !run.fastForward.removed ? callout("warn", "Not cleaned up:", ` temporary copies are still in the account: ${esc3(run.fastForward.copies.map((c) => `"${c.copyName}"`).join(", "))}. Delete them in GoHighLevel.`) : ""}${run.cleanup && !run.cleanup.removed ? callout("warn", "Test lead:", ` ${esc3(run.cleanup.detail)}`) : ""}`;
|
|
49654
|
+
const summary = `<div class="sum"><div class="cds-stack"><p>Finished ${when(run.finishedAt ?? run.startedAt)}. ${esc3(card.coverage.split(". Not checked")[0])}.</p>${leftovers}<div class="cds-row cds-no-print"><a class="cds-btn ghost" href="${href}&print=1" target="_blank" rel="noopener">Save as PDF</a></div></div>
|
|
49015
49655
|
${ringHtml(card.score, card.coverageLabel ?? card.coverage, true)}</div>`;
|
|
49016
49656
|
return {
|
|
49017
49657
|
head: pageHead(
|
|
@@ -49019,17 +49659,17 @@ ${ringHtml(card.score, card.coverageLabel ?? card.coverage, true)}</div>`;
|
|
|
49019
49659
|
"The report card is ready: the grade, what to fix, how the score was reached, and every step. Open it to read, save as a PDF or hand to your client.",
|
|
49020
49660
|
action
|
|
49021
49661
|
),
|
|
49022
|
-
rest: panel("Report card",
|
|
49662
|
+
rest: panel("Report card", esc3(clientName) || "This check", "", summary)
|
|
49023
49663
|
};
|
|
49024
49664
|
}
|
|
49025
49665
|
const minutesLeft = Math.max(0, Math.ceil((run.closesAt - now) / 6e4));
|
|
49026
49666
|
const planned = run.planNote.source !== "none" && run.expect.length > 0;
|
|
49027
49667
|
const held2 = f?.notYetDue.filter((m) => m.heldByWait) ?? [];
|
|
49028
49668
|
const title2 = !f ? "Watching what the test lead receives" : f.sent === 0 ? !watching && nothingArrivedHeading(run.steps) || `Nothing arrived in ${watching ? "the time so far" : `${run.watchMinutes} minutes`}` : f.problems.length || f.missing.length ? "What a new lead actually received" : held2.length ? "Not finished: GoHighLevel was still holding the test lead" : planned ? "A new lead got exactly what you planned" : "No problems in what the lead received";
|
|
49029
|
-
const head4 = pageHead(title2, `${productName()} made a test lead, then read every text and email this account sent it, word for word. ${
|
|
49669
|
+
const head4 = pageHead(title2, `${productName()} made a test lead, then read every text and email this account sent it, word for word. ${esc3(run.entryNote)}`, action);
|
|
49030
49670
|
const worst = f?.problems[0];
|
|
49031
49671
|
const quote2 = (tone, text3, cite) => `<div class="cds-callout quote${tone ? ` ${tone}` : ""}">${text3}<cite>${cite}</cite></div>`;
|
|
49032
|
-
const quoteHtml = f && f.sent === 0 ? quote2("warn", "The test lead received no text and no email.",
|
|
49672
|
+
const quoteHtml = f && f.sent === 0 ? quote2("warn", "The test lead received no text and no email.", esc3(nothingArrivedWhy(run.steps, run.draftWorkflows))) : worst ? quote2("bad", `${worst.kind === "blank_field" ? "A new lead with no first name on file got" : "A new lead got"} "${marked(worst.quote, "")}"`, `${esc3(worst.title)}${f && f.problems.length > 1 ? `. The first of ${f.problems.length}.` : "."}`) : f && held2.length && !f.missing.length ? quote2("warn", `${held2.length} planned message${held2.length === 1 ? " was" : "s were"} still waiting behind a wait GoHighLevel had not let go of.`, "Not judged. Run the check again.") : f && f.missing.length ? quote2("warn", `${f.missing.length} planned message${f.missing.length === 1 ? " never" : "s never"} arrived.`, "Everything that did arrive read correctly.") : f ? quote2("", `Every message read correctly${planned ? " and arrived when planned" : ""}.`, `Checked for the first ${run.watchMinutes} minutes. Later messages are listed below as not yet checked.`) : "";
|
|
49033
49673
|
const stats = f ? `<div class="stats">
|
|
49034
49674
|
${statTile(f.sent, "Messages sent")}
|
|
49035
49675
|
${statTile(f.problems.length, "A customer would notice", f.problems.length ? "bad" : "")}
|
|
@@ -49040,15 +49680,15 @@ ${(() => {
|
|
|
49040
49680
|
return opened ? statTile(`${f.links.ok} of ${opened}`, `Links open${f.links.skipped ? ` \xB7 ${f.links.skipped} not opened` : ""}`, f.links.broken ? "bad" : "") : statTile(0, `Links opened${f.links.skipped ? ` \xB7 ${f.links.skipped} not` : ""}`);
|
|
49041
49681
|
})()}
|
|
49042
49682
|
</div>` : "";
|
|
49043
|
-
const notGraded = !card.graded && run.status === "done" ? callout("warn", "Not graded.", ` ${
|
|
49683
|
+
const notGraded = !card.graded && run.status === "done" ? callout("warn", "Not graded.", ` ${esc3(card.reason)}`) : "";
|
|
49044
49684
|
const watchNote = watching ? callout("info", "", `<span><b>Still watching.</b> ${minutesLeft ? `About ${minutesLeft} minute${minutesLeft === 1 ? "" : "s"} left; this page updates itself and finishes on its own.` : "Finishing now."}${f ? " What you see below is a partial look." : ""}</span><button class="cds-btn ghost" type="button" id="look-now">Look now</button>`, ` role="status" id="watching"`) : "";
|
|
49045
|
-
const draftNote = run.draftWorkflows?.length ? callout("bad", "Still a draft, so it did nothing:", ` ${
|
|
49685
|
+
const draftNote = run.draftWorkflows?.length ? callout("bad", "Still a draft, so it did nothing:", ` ${esc3(run.draftWorkflows.join(", "))}. GoHighLevel accepts a lead added to a draft workflow and then runs none of its steps, with no error anywhere. Publish it, then run the Workflow Audit Pro again.`) : "";
|
|
49046
49686
|
const top = notGraded + watchNote + stats + quoteHtml + draftNote;
|
|
49047
49687
|
if (!f) return { head: head4, rest: top + aboutHtml(run) };
|
|
49048
49688
|
const fixes = f.problems.length ? panel("Fix these", "What a customer would have seen wrong", `${f.problems.length} problem${f.problems.length === 1 ? "" : "s"} \xB7 most serious first`, capped(f.problems.map(problemRow), list2)) : "";
|
|
49049
49689
|
const rows2 = [
|
|
49050
49690
|
...f.timeline.map(timelineRow),
|
|
49051
|
-
...f.missing.map((m) => `<li><span class="t">by ${mins(m.byMinute)}</span>${dot("warn")}<span class="what">Planned but never sent: ${
|
|
49691
|
+
...f.missing.map((m) => `<li><span class="t">by ${mins(m.byMinute)}</span>${dot("warn")}<span class="what">Planned but never sent: ${esc3(m.label)}<small>Its workflow may be a draft, its step may have failed, or a condition skipped it.</small></span>${chip("warn", "Missing")}</li>`)
|
|
49052
49692
|
];
|
|
49053
49693
|
const timeline = panel(
|
|
49054
49694
|
"Timeline",
|
|
@@ -49062,7 +49702,7 @@ ${(() => {
|
|
|
49062
49702
|
function coverageParts(run, f) {
|
|
49063
49703
|
const plan = run.planNote.source === "workflows" ? `Planned messages were read from the workflows: ${run.expect.length}.${run.planNote.conditional ? ` ${run.planNote.conditional} left out because they depend on what the lead does (an if/else or a wait for a reply).` : ""}${run.planNote.noWording ? ` ${run.planNote.noWording} left out because their wording is all merge fields.` : ""}${run.planNote.unread?.length ? ` ${run.planNote.unread.length} workflow${run.planNote.unread.length === 1 ? "" : "s"} could not be read.` : ""}${run.planNote.noPhone ? ` ${run.planNote.noPhone} text-message step${run.planNote.noPhone === 1 ? " was" : "s were"} not checked: no test phone was given.` : ""}` : run.planNote.source === "given" ? `Planned messages were given for this run: ${run.expect.length}.` : "No plan was known for this run, so nothing is reported as missing.";
|
|
49064
49704
|
const notYet = [
|
|
49065
|
-
...f.notYetDue.map((m) => m.heldByWait ? `${
|
|
49705
|
+
...f.notYetDue.map((m) => m.heldByWait ? `${esc3(m.label)}: GoHighLevel was still holding the test lead at ${esc3(run.heldAt?.map((h) => `"${h.stepName}"`).join(", ") ?? "a wait")} after it should have ended${run.extendedMinutes ? `, even after the check waited ${run.extendedMinutes} extra minutes` : ""}. Not judged; run the check again.` : `${esc3(m.label)}, due after the window closed.`),
|
|
49066
49706
|
`Messages after the ${run.watchMinutes}-minute window. Choose a longer watch on the next run.`,
|
|
49067
49707
|
"Booking an actual time, or replying as the lead.",
|
|
49068
49708
|
"Phone calls and voice agents.",
|
|
@@ -49091,20 +49731,20 @@ function coverageParts(run, f) {
|
|
|
49091
49731
|
"Coverage",
|
|
49092
49732
|
"What this check looked at",
|
|
49093
49733
|
"So a clean result means something",
|
|
49094
|
-
`${list2(checks.map((c) => `<li><span>${
|
|
49095
|
-
<p>${
|
|
49734
|
+
`${list2(checks.map((c) => `<li><span>${esc3(c)}</span></li>`).join(""))}
|
|
49735
|
+
<p>${esc3(plan)}</p>
|
|
49096
49736
|
<details><summary class="cds-link">Not checked (${notYet.length})</summary>${list2(notYet.map((n) => `<li><span>${n}</span></li>`).join(""))}</details>`
|
|
49097
49737
|
);
|
|
49098
49738
|
const ffLine = run.fastForward ? run.fastForward.removed ? " The temporary fast-forward copies were removed." : run.fastForward.problems?.length ? ` ${run.fastForward.problems.join(" ")}` : ` Temporary copies still in the account: ${run.fastForward.copies.map((c) => `"${c.copyName}"`).join(", ")}. The cockpit retries every 30 minutes while it runs; if they are still there tomorrow, delete them in GoHighLevel.` : "";
|
|
49099
49739
|
const leftover = !!run.cleanup && (!run.cleanup.removed || !!run.fastForward && !run.fastForward.removed);
|
|
49100
|
-
const cleanup = run.cleanup ? callout(leftover ? "warn" : "", run.cleanup.removed ? "Cleaned up:" : "Test lead:", ` ${
|
|
49740
|
+
const cleanup = run.cleanup ? callout(leftover ? "warn" : "", run.cleanup.removed ? "Cleaned up:" : "Test lead:", ` ${esc3(run.cleanup.detail + ffLine)} Anything that looked like a password or key in a message was hidden before it reached this page.`) : "";
|
|
49101
49741
|
return { coverage, cleanup };
|
|
49102
49742
|
}
|
|
49103
49743
|
function startForm(d, inlineSubmit) {
|
|
49104
49744
|
if (!d.client) return "";
|
|
49105
49745
|
const form = `<form id="start-form" class="startform">
|
|
49106
49746
|
<fieldset class="cds-field"><legend class="lbl">Workflows the test lead enters</legend><div class="wf" id="wf-list"><p class="hint">Loading this account's workflows\u2026</p></div></fieldset>
|
|
49107
|
-
<div class="cds-field"><label for="owner-email">Your email</label><div class="hint" id="owner-email-hint">The test lead uses it, so every message comes to you.</div><input class="cds-input" id="owner-email" name="ownerEmail" type="email" required value="${
|
|
49747
|
+
<div class="cds-field"><label for="owner-email">Your email</label><div class="hint" id="owner-email-hint">The test lead uses it, so every message comes to you.</div><input class="cds-input" id="owner-email" name="ownerEmail" type="email" required value="${esc3(d.ownerEmail ?? "")}" autocomplete="email" aria-describedby="owner-email-hint"></div>
|
|
49108
49748
|
<div class="cds-field"><label for="test-phone">Test phone, optional</label><div class="hint" id="test-phone-hint">A phone you own; texts will really send to it.</div><input class="cds-input" id="test-phone" name="testPhone" type="tel" autocomplete="tel" placeholder="+1 555 010 0100" aria-describedby="test-phone-hint"></div>
|
|
49109
49749
|
<div class="cds-field"><label for="watch">Watch for</label><select class="cds-select" id="watch" name="watchMinutes"><option value="auto" selected>Automatic: until the last planned message, plus 2 minutes</option><option value="10">10 minutes</option><option value="30">30 minutes</option><option value="60">1 hour</option><option value="240">4 hours</option><option value="1440">24 hours</option></select></div>
|
|
49110
49750
|
|
|
@@ -49116,10 +49756,10 @@ ${callout("warn", "", "The test lead has no first name, on purpose. The workflow
|
|
|
49116
49756
|
<div id="start-ask"></div>
|
|
49117
49757
|
<p class="cds-callout result" id="start-result" role="status" aria-live="polite"></p>
|
|
49118
49758
|
</form>`;
|
|
49119
|
-
return panel("New check", `${d.run?.status === "done" ? "Run another check" : "Run the Workflow Audit Pro"} on ${
|
|
49759
|
+
return panel("New check", `${d.run?.status === "done" ? "Run another check" : "Run the Workflow Audit Pro"} on ${esc3(d.client.name)}`, "Sends one test lead, then lists the contacts that carry its address", form, ` id="start"`, "cds-no-print");
|
|
49120
49760
|
}
|
|
49121
49761
|
function shopperPageHtml(d, seatToken, seatHeader) {
|
|
49122
|
-
const picker = `<form class="picker cds-no-print" method="get" action="/shopper"><div class="cds-field"><label for="client">Client account</label><select class="cds-select" id="client" name="client" data-picker="" onchange="this.form.submit()"><option value="">Choose a client\u2026</option>${d.accounts.map((a) => `<option value="${
|
|
49762
|
+
const picker = `<form class="picker cds-no-print" method="get" action="/shopper"><div class="cds-field"><label for="client">Client account</label><select class="cds-select" id="client" name="client" data-picker="" onchange="this.form.submit()"><option value="">Choose a client\u2026</option>${d.accounts.map((a) => `<option value="${esc3(a.locationId)}"${d.client?.locationId === a.locationId ? ' selected=""' : ""}>${esc3(a.name)}</option>`).join("")}</select></div><noscript><button class="cds-btn ghost" type="submit">Open</button></noscript></form>`;
|
|
49123
49763
|
const run = d.run;
|
|
49124
49764
|
const graded2 = !!(run && run.status === "done" && run.findings && scoreRun(run).graded);
|
|
49125
49765
|
const canStart = !!d.client && run?.status !== "watching";
|
|
@@ -49128,7 +49768,7 @@ function shopperPageHtml(d, seatToken, seatHeader) {
|
|
|
49128
49768
|
if (!d.client) {
|
|
49129
49769
|
body = pageHead("See what a new lead actually receives", `Pick a client account. ${productName()} makes a test lead with your own email address, puts it through the workflows you choose, then reads every text and email the account sent it, word for word, and tells you what a real customer would have seen wrong.`) + picker;
|
|
49130
49770
|
} else if (!run) {
|
|
49131
|
-
body = pageHead(`No check has run on ${
|
|
49771
|
+
body = pageHead(`No check has run on ${esc3(d.client.name)} yet`, "Choose the workflows a new lead goes through, usually speed-to-lead and the welcome sequence, then start.", action) + picker + startForm(d, false);
|
|
49132
49772
|
} else {
|
|
49133
49773
|
const r = resultHtml(run, d.now, d.client.name, action);
|
|
49134
49774
|
body = r.head + picker + r.rest + (canStart ? startForm(d, graded2) : "");
|
|
@@ -49292,7 +49932,7 @@ ${COCKPIT_TYPE_JS}
|
|
|
49292
49932
|
})();
|
|
49293
49933
|
</script>`;
|
|
49294
49934
|
}
|
|
49295
|
-
var
|
|
49935
|
+
var esc3, chip, pageHead, panel, callout, statTile, list2, tl, OUTCOME, PROBLEM_CHIP, dot, CHIP, CHANNEL, mins, when, reportHref, CSS;
|
|
49296
49936
|
var init_page = __esm({
|
|
49297
49937
|
"src/command-os/shopper/page.ts"() {
|
|
49298
49938
|
"use strict";
|
|
@@ -49302,12 +49942,12 @@ var init_page = __esm({
|
|
|
49302
49942
|
init_fastforward();
|
|
49303
49943
|
init_steps();
|
|
49304
49944
|
init_score();
|
|
49305
|
-
|
|
49306
|
-
chip = (tone, text3) => `<span class="cds-chip${tone ? ` ${tone}` : ""}">${
|
|
49945
|
+
esc3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
49946
|
+
chip = (tone, text3) => `<span class="cds-chip${tone ? ` ${tone}` : ""}">${esc3(text3)}</span>`;
|
|
49307
49947
|
pageHead = (title2, sub, action = "") => `<div class="cds-page-head"><div class="txt"><div class="cds-kicker">Workflow Audit Pro</div><h1 class="cds-page-title">${title2}</h1><p class="cds-page-sub">${sub}</p></div>${action}</div>`;
|
|
49308
49948
|
panel = (label2, title2, meta, inner, attrs = "", cls = "") => `<section class="cds-panel${cls ? ` ${cls}` : ""}"${attrs}><div class="cds-panel-head"><div class="txt"><div class="cds-panel-label">${label2}</div><h2 class="cds-panel-title">${title2}</h2></div>${meta ? `<div class="cds-panel-meta">${meta}</div>` : ""}</div>${inner}</section>`;
|
|
49309
49949
|
callout = (tone, title2, inner, attrs = "") => `<div class="cds-callout${tone ? ` ${tone}` : ""}"${attrs}>${title2 ? `<span class="ttl">${title2}</span>` : ""}${inner}</div>`;
|
|
49310
|
-
statTile = (value, label2, tone = "") => `<div class="cds-stat"><div class="big${tone ? ` ${tone}` : ""}">${
|
|
49950
|
+
statTile = (value, label2, tone = "") => `<div class="cds-stat"><div class="big${tone ? ` ${tone}` : ""}">${esc3(String(value))}</div><div class="lbl">${esc3(label2)}</div></div>`;
|
|
49311
49951
|
list2 = (inner) => `<ul class="cds-list">${inner}</ul>`;
|
|
49312
49952
|
tl = (inner) => `<ol class="cds-tl">${inner}</ol>`;
|
|
49313
49953
|
OUTCOME = {
|
|
@@ -49587,8 +50227,8 @@ function brandAccentStyle(b) {
|
|
|
49587
50227
|
}
|
|
49588
50228
|
function brandBarHtml(b, product) {
|
|
49589
50229
|
const src = safeLogoSrc(b.logoUrl);
|
|
49590
|
-
const logo = src ? `<img class="rp-logo" src="${
|
|
49591
|
-
return `<div class="rp-brand">${logo}${
|
|
50230
|
+
const logo = src ? `<img class="rp-logo" src="${esc4(src)}" alt="${esc4(b.name)}">` : "";
|
|
50231
|
+
return `<div class="rp-brand">${logo}${esc4(b.name)} <span>${esc4(product)}</span></div>`;
|
|
49592
50232
|
}
|
|
49593
50233
|
function brandChooserSaveJs(save) {
|
|
49594
50234
|
return `(function(){var f=document.querySelector("form.rp-choose");if(!f||!window.fetch)return;
|
|
@@ -49609,13 +50249,13 @@ f.addEventListener("submit",function(e){var b=e.submitter;if(!b||!b.value)return
|
|
|
49609
50249
|
again.addEventListener("click",function(){if(last)pick(last)})})();`;
|
|
49610
50250
|
}
|
|
49611
50251
|
function brandChooserHtml(b, form) {
|
|
49612
|
-
const hidden2 = Object.entries(form.params).map(([k, v]) => `<input type="hidden" name="${
|
|
50252
|
+
const hidden2 = Object.entries(form.params).map(([k, v]) => `<input type="hidden" name="${esc4(k)}" value="${esc4(v)}">`).join("");
|
|
49613
50253
|
const opt = (m, disabled = false) => `<button type="submit" name="brand" value="${m}" aria-pressed="${b.mode === m}"${disabled ? ` disabled title="Save your agency name in Your agency first"` : ""}>${REPORT_BRAND_LABELS[m]}</button>`;
|
|
49614
|
-
const notes = b.notes.length ? `<div class="rp-notes">${b.notes.map((n) => `<p class="rp-soon">${
|
|
50254
|
+
const notes = b.notes.length ? `<div class="rp-notes">${b.notes.map((n) => `<p class="rp-soon">${esc4(n)}</p>`).join("")}</div>` : "";
|
|
49615
50255
|
const save = form.save ? `<p class="rp-soon" id="rp-choose-status" role="status" hidden></p><button type="button" class="cds-btn ghost" id="rp-choose-retry" hidden>Try again</button><script>${brandChooserSaveJs(form.save)}</script>` : "";
|
|
49616
|
-
return `<form class="rp-choose" method="get" action="${
|
|
50256
|
+
return `<form class="rp-choose" method="get" action="${esc4(form.action)}"><span>Branding on this report</span>${hidden2}<span class="cds-seg">${opt("agency", !b.agencyAvailable)}${opt("neutral")}${opt("standard")}</span></form>${save}${notes}`;
|
|
49617
50257
|
}
|
|
49618
|
-
var crypto11, import_node_child_process3, fs33, path32, REPORT_BRAND_MODES, REPORT_BRAND_LABELS, LEGACY_MODES, MIN_ACCENT_CONTRAST, TEXT_BACKGROUNDS, GHL_FOOTER, LOGO_MAX_BYTES, LOGO_SOURCE_MAX_BYTES, preparing, notSmallEnough, fileKey, RASTER_TYPES, LOGO_DATA_URI, REPORT_BRAND_CHOICES_ENV, LOCATION_ID,
|
|
50258
|
+
var crypto11, import_node_child_process3, fs33, path32, REPORT_BRAND_MODES, REPORT_BRAND_LABELS, LEGACY_MODES, MIN_ACCENT_CONTRAST, TEXT_BACKGROUNDS, GHL_FOOTER, LOGO_MAX_BYTES, LOGO_SOURCE_MAX_BYTES, preparing, notSmallEnough, fileKey, RASTER_TYPES, LOGO_DATA_URI, REPORT_BRAND_CHOICES_ENV, LOCATION_ID, esc4, BRAND_LAYOUT_CSS, BRAND_LOGO_JS, BRAND_SAVE_UNREACHABLE;
|
|
49619
50259
|
var init_report_brand = __esm({
|
|
49620
50260
|
"src/command-os/report-brand.ts"() {
|
|
49621
50261
|
"use strict";
|
|
@@ -49650,7 +50290,7 @@ var init_report_brand = __esm({
|
|
|
49650
50290
|
LOGO_DATA_URI = /^data:image\/(png|jpeg|webp|gif|svg\+xml);base64,[A-Za-z0-9+/=]+$/;
|
|
49651
50291
|
REPORT_BRAND_CHOICES_ENV = "GHL_MCP_REPORT_BRAND_CHOICES";
|
|
49652
50292
|
LOCATION_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
49653
|
-
|
|
50293
|
+
esc4 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
49654
50294
|
BRAND_LAYOUT_CSS = `
|
|
49655
50295
|
.rp-brand{display:flex;align-items:center;min-width:0}
|
|
49656
50296
|
.rp-logo{display:block;height:32px;width:auto;max-width:180px;object-fit:contain;margin-right:var(--space-3)}
|
|
@@ -49667,50 +50307,50 @@ var init_report_brand = __esm({
|
|
|
49667
50307
|
function gradeRing(score2, caption, complete, size2 = 176) {
|
|
49668
50308
|
const s = Math.max(0, Math.min(100, Math.round(score2)));
|
|
49669
50309
|
const r = 52, c = 2 * Math.PI * r, g = gradeOf(s), k = complete ? gcls(s) : "cds-g-incomplete";
|
|
49670
|
-
return `<div class="cds-ring" role="img" aria-label="Grade ${g}, ${s} out of 100${complete ? "" : ""}. ${
|
|
50310
|
+
return `<div class="cds-ring" role="img" aria-label="Grade ${g}, ${s} out of 100${complete ? "" : ""}. ${esc5(caption)}"><svg width="${size2}" height="${size2}" viewBox="0 0 120 120"><circle class="trk" cx="60" cy="60" r="${r}" stroke-width="10"></circle><circle class="arc ${k}" cx="60" cy="60" r="${r}" stroke-width="10" stroke-dasharray="${c.toFixed(2)}" stroke-dashoffset="${(c * (1 - s / 100)).toFixed(2)}" transform="rotate(-90 60 60)"></circle><text class="letter ${k}" x="60" y="66" text-anchor="middle" font-size="${size2 < 120 ? 30 : 44}">${g}</text><text class="out" x="60" y="86" text-anchor="middle">${s}/100</text></svg>${caption ? `<div class="cap">${esc5(caption)}</div>` : ""}</div>`;
|
|
49671
50311
|
}
|
|
49672
50312
|
function scoreBars(items, source) {
|
|
49673
50313
|
return `<div><div class="cds-bars">${items.map((it) => {
|
|
49674
50314
|
const k = gcls(it.score);
|
|
49675
|
-
return `<div class="cds-bar-row"><span class="name">${
|
|
49676
|
-
}).join("")}</div><div class="cds-src">${
|
|
50315
|
+
return `<div class="cds-bar-row"><span class="name">${esc5(it.label)}</span><span class="trk"><i class="fill ${k}" style="width:${it.score}%"></i></span><span class="val"><span class="${k}">${gradeOf(it.score)}</span> ${it.score}</span></div>`;
|
|
50316
|
+
}).join("")}</div><div class="cds-src">${esc5(source)}</div></div>`;
|
|
49677
50317
|
}
|
|
49678
50318
|
function deductions(items, capped2) {
|
|
49679
50319
|
const total2 = items.reduce((a, d) => a + d.points, 0);
|
|
49680
|
-
return `<ul class="cds-ded">${items.map((d) => `<li class="${d.points ? "" : "excused"}"><span class="pts">${d.points ? `−${d.points}` : "0"}</span><span>${
|
|
50320
|
+
return `<ul class="cds-ded">${items.map((d) => `<li class="${d.points ? "" : "excused"}"><span class="pts">${d.points ? `−${d.points}` : "0"}</span><span>${esc5(d.text)}${d.cap !== void 0 && d.points ? `<span class="cap">${esc5(capText(d.cap))}</span>` : ""}${d.causedBy ? `<span class="note">${esc5(d.causedBy)}</span>` : ""}</span></li>`).join("")}<li class="total"><span class="pts">−${total2}</span><span>Points lost in total${capped2 !== void 0 ? `, then capped at ${capped2}` : ""}</span></li></ul>`;
|
|
49681
50321
|
}
|
|
49682
50322
|
function workflowFlow(steps) {
|
|
49683
50323
|
return `<div class="cds-flow">${steps.map((r) => {
|
|
49684
50324
|
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : r.notTested ? ["", "Not tested"] : OUTCOME2[r.outcome] ?? ["", "Unknown"];
|
|
49685
|
-
return `<div class="cds-node ${tone === "bad" || tone === "warn" ? "hit" : ""}"><span class="pin ${tone}"></span><span class="nm">${
|
|
50325
|
+
return `<div class="cds-node ${tone === "bad" || tone === "warn" ? "hit" : ""}"><span class="pin ${tone}"></span><span class="nm">${esc5(r.stepName)}${r.detail ? `<small>${esc5(r.detail)}</small>` : ""}</span>${chip2(tone === "wait" ? "" : tone, word)}</div>`;
|
|
49686
50326
|
}).join("")}</div>`;
|
|
49687
50327
|
}
|
|
49688
50328
|
function fixControls(fx, key2, why) {
|
|
49689
50329
|
if (!fx) return `<p class="rp-soon cds-no-print">Fixes can be applied when this report is open in the cockpit.</p>`;
|
|
49690
50330
|
const offer = key2 ? fx.offers[key2] : void 0;
|
|
49691
|
-
if (!key2 || !offer?.kind) return `<p class="rp-soon cds-no-print">${
|
|
50331
|
+
if (!key2 || !offer?.kind) return `<p class="rp-soon cds-no-print">${esc5(offer?.why ?? why ?? "This one needs a decision a button cannot make, so change it in GoHighLevel.")}</p>`;
|
|
49692
50332
|
const st = fx.statuses[key2];
|
|
49693
50333
|
const when2 = st ? localTime2(st.at) : "";
|
|
49694
50334
|
const [tone, word] = st?.state === "applied" && st.held !== true ? ["bad", "Check it"] : st?.reason === "parked" ? ["warn", "Not changed yet"] : st ? FIX_STATE[st.state] : ["", ""];
|
|
49695
|
-
const technical = st?.technical ? `<details class="cds-details"><summary>Details</summary><pre class="cds-code">${
|
|
49696
|
-
const state2 = !st ? "" : `<div class="cds-row rp-fixstate">${chip2(tone, word)}<span class="rp-soon">${
|
|
50335
|
+
const technical = st?.technical ? `<details class="cds-details"><summary>Details</summary><pre class="cds-code">${esc5(st.technical)}</pre></details>` : "";
|
|
50336
|
+
const state2 = !st ? "" : `<div class="cds-row rp-fixstate">${chip2(tone, word)}<span class="rp-soon">${esc5(
|
|
49697
50337
|
st.state === "undone" ? `Undone ${when2}. The step is back the way it was.` : st.state === "not_written" ? `Tried ${when2}. ${st.detail ?? ""} Nothing was changed.${st.todo ? ` ${st.todo}` : ""}` : st.state === "undo_failed" ? `Undo tried ${when2}. ${st.detail ?? ""}` : st.state === "writing" ? `Started ${when2}, and no answer was recorded. Look at the workflow in GoHighLevel.` : `Changed ${when2}. ${st.detail ?? ""}`
|
|
49698
50338
|
)}</span></div>${technical}`;
|
|
49699
50339
|
const inp = offer.input;
|
|
49700
|
-
const field2 = inp ? `<div class="cds-field" data-part="input" hidden><label for="fx-${
|
|
50340
|
+
const field2 = inp ? `<div class="cds-field" data-part="input" hidden><label for="fx-${esc5(key2)}">${esc5(inp.label)}</label><input class="cds-input" id="fx-${esc5(key2)}" type="${inp.kind === "url" ? "url" : "text"}" value="${esc5(inp.value ?? "")}" autocomplete="off"><span class="hint">${esc5(inp.hint)}</span><div class="cds-row"><button class="cds-btn" type="button" data-act="preview" disabled>Show the change</button></div></div>` : "";
|
|
49701
50341
|
const settled = !st || st.state === "not_written" || st.state === "undone";
|
|
49702
50342
|
const actions = st?.canUndo ? `<div class="cds-row" data-part="actions"><button class="cds-btn ghost" type="button" data-act="undo" disabled>Undo this fix</button></div>` : !settled ? "" : `<div class="cds-row" data-part="actions"><button class="cds-btn" type="button" data-act="start" disabled>Apply this fix</button><button class="cds-btn ghost" type="button" data-act="later">Not now</button></div>`;
|
|
49703
|
-
return `<div class="rp-fixbox cds-no-print" data-fix="${
|
|
50343
|
+
return `<div class="rp-fixbox cds-no-print" data-fix="${esc5(key2)}"${inp?.required || inp?.confirm && inp.value ? ` data-input-first="1"` : ""}>${state2}${field2}${actions}<div class="cds-row" data-part="later" hidden><span class="rp-soon">Set aside. Nothing was changed.</span><button class="cds-btn ghost" type="button" data-act="again">Show the fix</button></div><div class="rp-fixbox" data-part="preview" hidden></div><p class="rp-soon" data-part="msg" role="status" aria-live="polite"></p></div>`;
|
|
49704
50344
|
}
|
|
49705
50345
|
function fixCard(title2, points, detail, now, after, controls) {
|
|
49706
|
-
return `<div class="cds-fix"><div class="cds-row fixhead"><h4>${
|
|
50346
|
+
return `<div class="cds-fix"><div class="cds-row fixhead"><h4>${esc5(title2)}</h4>${chip2("bad", `\u2212${points} points`)}</div>${detail ? `<p>${esc5(detail)}</p>` : ""}<div class="cds-diff"><div class="was"><span class="k">Now</span>${now}</div><div class="now"><span class="k">After the fix</span>${esc5(after)}</div></div>${controls}</div>`;
|
|
49707
50347
|
}
|
|
49708
50348
|
function beforeAfter(before, after, complete) {
|
|
49709
50349
|
return `<div class="cds-ba"><div><div class="lbl">Before fixes</div>${gradeRing(before, "", true, 112)}</div><span class="arrow" aria-hidden="true">\u2192</span><div><div class="lbl">After fixes</div>${gradeRing(after, "", true, 112)}</div></div>`;
|
|
49710
50350
|
}
|
|
49711
50351
|
function marked2(quote2, highlight) {
|
|
49712
50352
|
const i = highlight ? quote2.indexOf(highlight) : -1;
|
|
49713
|
-
return i < 0 ?
|
|
50353
|
+
return i < 0 ? esc5(quote2) : `${esc5(quote2.slice(0, i))}<mark>${esc5(highlight)}</mark>${esc5(quote2.slice(i + highlight.length))}`;
|
|
49714
50354
|
}
|
|
49715
50355
|
function reportDocumentHtml(run, card, opts) {
|
|
49716
50356
|
const fx = opts.fixes;
|
|
@@ -49757,11 +50397,11 @@ function reportDocumentHtml(run, card, opts) {
|
|
|
49757
50397
|
].join("");
|
|
49758
50398
|
const worst = f.problems[0];
|
|
49759
50399
|
const sentAt = worst ? f.timeline.find((t) => t.messageId === worst.messageId) : void 0;
|
|
49760
|
-
const quote2 = worst ? callout2(`A new lead got "${marked2(worst.quote, "")}"`, { quote: true, tone: "bad", cite: `${worst.sentBy ?? worst.title}${sentAt ? `, sent ${clock3(run.startedAt + sentAt.minute * 6e4)}` : ""}${worst.kind === "blank_field" && !run.leadHasFirstName ? " to a lead with no first name on file" : ""}` }) : stepProblems[0] ? callout2(`"${
|
|
50400
|
+
const quote2 = worst ? callout2(`A new lead got "${marked2(worst.quote, "")}"`, { quote: true, tone: "bad", cite: `${worst.sentBy ?? worst.title}${sentAt ? `, sent ${clock3(run.startedAt + sentAt.minute * 6e4)}` : ""}${worst.kind === "blank_field" && !run.leadHasFirstName ? " to a lead with no first name on file" : ""}` }) : stepProblems[0] ? callout2(`"${esc5(stepProblems[0].stepName)}" ${stepProblems[0].kind === "did_nothing" ? "did nothing for the test lead, and the workflow carried on as if it had worked" : stepProblems[0].kind.startsWith("failed") ? "failed" : "was skipped"}.`, { quote: true, tone: "bad", cite: stepProblems[0].workflowName }) : run.draftWorkflows?.length ? callout2(`A chosen workflow is still a draft, so GoHighLevel took the test lead and ran none of its steps.`, { quote: true, tone: "bad", cite: run.draftWorkflows.join(", ") }) : callout2(f.sent ? "Nothing a customer would notice: every message that arrived read correctly." : "No message arrived during the check.", { quote: true, tone: f.sent ? "info" : "warn" });
|
|
49761
50401
|
const stepTitle = (p) => POINTS[p.kind].what;
|
|
49762
|
-
const stepCard = (p, controls) => fixCard(stepTitle(p), POINTS[p.kind].points, p.detail,
|
|
50402
|
+
const stepCard = (p, controls) => fixCard(stepTitle(p), POINTS[p.kind].points, p.detail, esc5(`"${p.stepName}" in ${p.workflowName}`), p.fix, controls);
|
|
49763
50403
|
const DRAFT_WHY = "Publishing starts sending to every real lead, so publish it yourself in GoHighLevel when it is ready.";
|
|
49764
|
-
const draftCard = (d) => fixCard("Still a draft, so it did nothing", POINTS.did_nothing.points, "GoHighLevel took the test lead into this workflow and ran none of its steps, with no error anywhere.",
|
|
50404
|
+
const draftCard = (d) => fixCard("Still a draft, so it did nothing", POINTS.did_nothing.points, "GoHighLevel took the test lead into this workflow and ran none of its steps, with no error anywhere.", esc5(`${d}: draft`), "Publish the workflow, then run the check again", fixControls(fx, void 0, DRAFT_WHY));
|
|
49765
50405
|
const elsewhere = fx ? `<p class="rp-soon cds-no-print"><a href="#fixes">The fix for this step is under Fix these, above.</a></p>` : fixControls(fx, void 0);
|
|
49766
50406
|
const cards = [
|
|
49767
50407
|
...(run.draftWorkflows ?? []).map((d) => [POINTS.did_nothing.points, draftCard(d)]),
|
|
@@ -49772,7 +50412,7 @@ function reportDocumentHtml(run, card, opts) {
|
|
|
49772
50412
|
const fixCards = cards.sort((a, b) => b[0] - a[0]).map(([, h]) => h);
|
|
49773
50413
|
const projected = 100;
|
|
49774
50414
|
const fixesHead = fixCards.length ? `${n2w(fixCards.length)} ${fixCards.length === 1 ? "fix would take" : "fixes would take"} this to ${gradeOf(card.score) === gradeOf(projected) ? projected : `an ${gradeOf(projected)}`}` : "Nothing to fix";
|
|
49775
|
-
const s5 = `<section data-section="fixes" id="fixes" class="rp-section"${fx ? ` data-client="${
|
|
50415
|
+
const s5 = `<section data-section="fixes" id="fixes" class="rp-section"${fx ? ` data-client="${esc5(run.locationId)}" data-run="${esc5(run.runId)}"` : ""}><div class="rp-head"><div><div class="rp-label">Fix these</div><h2>${esc5(fixesHead)}</h2></div><div class="rp-right">Each fix changes only the step shown, and only after you approve it</div></div>${fixCards.length ? `<div class="rp-two">${fixCards.join("")}</div>` : `<div class="rp-card"><p class="rp-meta">Every check that ran passed, so there is nothing to change.</p></div>`}</section>`;
|
|
49776
50416
|
const s6 = `<section data-section="proof" id="proof" class="rp-section"><div class="rp-two">
|
|
49777
50417
|
<div class="rp-card"><div class="rp-card-head"><h3>After the fixes</h3><span class="rp-tag">Projected until re\u2011run</span></div>${beforeAfter(card.score, charged2.length ? projected : card.score, card.complete)}<p class="rp-meta">The after grade is confirmed only by running the same check again.</p></div>
|
|
49778
50418
|
<div class="rp-card"><h3>How ${card.score} was reached</h3>${card.deductions.length ? deductions(card.deductions, card.capped?.at) : `<p class="rp-meta">No points lost: every check that ran passed.</p>`}</div>
|
|
@@ -49785,7 +50425,7 @@ function reportDocumentHtml(run, card, opts) {
|
|
|
49785
50425
|
...st.counts.unclear ? [`${st.counts.unclear} step${st.counts.unclear === 1 ? " has" : "s have"} a status this version cannot read; check GoHighLevel's Execution logs.`] : []
|
|
49786
50426
|
];
|
|
49787
50427
|
const multi = byWf.size > 1;
|
|
49788
|
-
const stepsCard = `<div class="rp-card rp-flowcard"><div class="rp-card-head"><div><div class="rp-label">Every step</div><h3>What each step really did</h3></div><span class="rp-tag">From GoHighLevel's own log</span></div>${stepNotes.length ? callout2(stepNotes.map(
|
|
50428
|
+
const stepsCard = `<div class="rp-card rp-flowcard"><div class="rp-card-head"><div><div class="rp-label">Every step</div><h3>What each step really did</h3></div><span class="rp-tag">From GoHighLevel's own log</span></div>${stepNotes.length ? callout2(stepNotes.map(esc5).join("<br>"), { tone: "warn" }) : ""}${[...byWf.values()].map((w) => `${multi ? `<div class="rp-tag">${esc5(w.name)}</div>` : ""}${workflowFlow([...w.steps.values()])}`).join("")}</div>`;
|
|
49789
50429
|
const entered = (st?.read ? st.rows : []).find((r) => r.outcome === "entered");
|
|
49790
50430
|
const tlRows = [
|
|
49791
50431
|
...entered ? [`<li><span class="t">+0:00</span><span class="dot ok"></span><span class="what">Entered the workflow</span><span></span></li>`] : [],
|
|
@@ -49794,7 +50434,7 @@ function reportDocumentHtml(run, card, opts) {
|
|
|
49794
50434
|
const mins2 = Math.floor(t.minute), sec = Math.round((t.minute - mins2) * 60);
|
|
49795
50435
|
const stepName = t.sentBy ? t.sentBy.slice(t.sentBy.lastIndexOf(": ") + 2) : t.channel === "email" ? t.subject ?? t.preview : t.preview;
|
|
49796
50436
|
const problem = f.problems.find((p) => p.messageId === t.messageId);
|
|
49797
|
-
return `<li><span class="t">+${mins2}:${String(sec).padStart(2, "0")}</span><span class="dot ${tone}"></span><span class="what">${
|
|
50437
|
+
return `<li><span class="t">+${mins2}:${String(sec).padStart(2, "0")}</span><span class="dot ${tone}"></span><span class="what">${esc5(stepName)}${problem ? `<small>"${esc5(problem.quote)}"</small>` : ""}</span>${chip2(tone, t.problems.length ? t.problems[0] === "blank_field" ? "Blank name" : "Problem" : "Arrived")}</li>`;
|
|
49798
50438
|
})
|
|
49799
50439
|
].join("");
|
|
49800
50440
|
const broke = stepProblems.slice().sort((a, b) => POINTS[b.kind].points - POINTS[a.kind].points)[0];
|
|
@@ -49813,40 +50453,40 @@ function reportDocumentHtml(run, card, opts) {
|
|
|
49813
50453
|
const leftCopies = ff && !ff.removed ? ff.problems?.length ? ff.problems.join(" ") : `Temporary copies still in the account: ${ff.copies.map((c) => `"${c.copyName}"`).join(", ")}. Delete them in GoHighLevel.` : "";
|
|
49814
50454
|
const cleanupText = [run.cleanup?.detail ?? "", leftCopies || (ff?.removed ? "The temporary fast-forward copies were removed." : ""), "Anything that looked like a password or key in a message was hidden before it reached this report."].filter(Boolean).join(" ");
|
|
49815
50455
|
const cleanedUp = !leftCopies && run.cleanup?.removed === true;
|
|
49816
|
-
const s9 = `<section data-section="coverage" class="rp-band">${callout2(`${
|
|
50456
|
+
const s9 = `<section data-section="coverage" class="rp-band">${callout2(`${esc5(notTested.map((g) => g.charAt(0).toUpperCase() + g.slice(1)).join(". "))}.`, { tone: "info", title: "Not tested in this run" })}<div class="rp-gap"></div>${callout2(esc5(cleanupText), { tone: cleanedUp ? "info" : "warn", title: cleanedUp ? "Cleaned up" : leftCopies ? "Not cleaned up" : "Test lead" })}</section>`;
|
|
49817
50457
|
const fixed = Object.values(fx?.statuses ?? {}).some((st2) => st2.state !== "undone" && st2.state !== "not_written");
|
|
49818
50458
|
const source = fixed ? "From GoHighLevel's execution log and the test lead's inbox. The check itself changed nothing; each fix you approved is shown on its card." : ff ? "From GoHighLevel's execution log and the test lead's inbox. The original workflows were only read." : "From GoHighLevel's execution log and the test lead's inbox. Nothing in the workflows was changed.";
|
|
49819
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${
|
|
50459
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc5(`${product} \xB7 ${opts.clientName}`)}</title>
|
|
49820
50460
|
<style>${DS_TOKENS_CSS}</style>
|
|
49821
50461
|
<style>${DS_BUNDLE_CSS}</style>
|
|
49822
50462
|
<style>${LAYOUT_CSS}</style>${brandAccentStyle(brand)}</head><body>
|
|
49823
50463
|
<div class="rp${brand.accent ? " rp-branded" : ""}" data-brand="${brand.mode}">
|
|
49824
|
-
<div class="rp-tools cds-no-print">${opts.chooser ? brandChooserHtml(brand, opts.chooser) : ""}${opts.backHref ? `<a class="cds-btn ghost" href="${
|
|
50464
|
+
<div class="rp-tools cds-no-print">${opts.chooser ? brandChooserHtml(brand, opts.chooser) : ""}${opts.backHref ? `<a class="cds-btn ghost" href="${esc5(opts.backHref)}">Back to ${esc5(brand.mode === "standard" ? OWN_PRODUCT : "the audit")}</a>` : ""}<button class="cds-btn" type="button" id="save-pdf">Save as PDF</button></div><!--/rp-tools-->
|
|
49825
50465
|
<header data-section="bar" class="rp-bar">${brandBarHtml(brand, product)}<nav class="rp-nav"><a class="on" href="#fixes">Fixes</a><a href="#proof">Proof</a><a href="#detail">Detail</a></nav></header>
|
|
49826
|
-
<section data-section="hero" class="rp-hero"><div class="rp-hero-text"><div class="rp-kicker">Verify \xB7 ${
|
|
50466
|
+
<section data-section="hero" class="rp-hero"><div class="rp-hero-text"><div class="rp-kicker">Verify \xB7 ${esc5(product)} \xB7 ${esc5(opts.clientName)}</div><h1>${esc5(h1)}</h1><p class="rp-sub">${esc5(sub)}</p><p class="rp-meta">${esc5(meta)}</p><p class="rp-meta">${esc5(run.entryNote)}</p></div><div class="rp-ringbox">${gradeRing(card.score, ringCaption, true)}</div></section>
|
|
49827
50467
|
<section data-section="tiles" class="rp-band"><div class="rp-tiles">${tiles}</div></section>
|
|
49828
50468
|
<section data-section="quote" class="rp-band">${quote2}</section>
|
|
49829
50469
|
${s5}${s6}${s7}${s8}${s9}
|
|
49830
|
-
<footer data-section="footer" class="rp-foot"><span>${
|
|
50470
|
+
<footer data-section="footer" class="rp-foot"><span>${esc5(source)}</span><span>${esc5(brand.footer)}</span></footer>
|
|
49831
50471
|
</div>
|
|
49832
50472
|
<script>${BRAND_LOGO_JS}document.getElementById("save-pdf").addEventListener("click",function(){window.print()});if(/[?&]print=1(&|$)/.test(location.search))window.addEventListener("load",function(){setTimeout(function(){window.print()},300)});</script>
|
|
49833
50473
|
${fx ? `<script>${FIX_SCRIPT}</script>` : ""}
|
|
49834
50474
|
</body></html>`;
|
|
49835
50475
|
}
|
|
49836
|
-
var
|
|
50476
|
+
var esc5, n2w, localTime2, gcls, chip2, statTile2, callout2, OUTCOME2, FIX_STATE, FIX_SCRIPT, LAYOUT_CSS, OWN_PRODUCT, NEUTRAL_PRODUCT;
|
|
49837
50477
|
var init_report_page = __esm({
|
|
49838
50478
|
"src/command-os/shopper/report-page.ts"() {
|
|
49839
50479
|
"use strict";
|
|
49840
50480
|
init_report_css_generated();
|
|
49841
50481
|
init_report_brand();
|
|
49842
50482
|
init_score();
|
|
49843
|
-
|
|
50483
|
+
esc5 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
49844
50484
|
n2w = (n) => ["No", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"][n] ?? String(n);
|
|
49845
50485
|
localTime2 = (ms) => new Date(ms).toLocaleString("en-US", { weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
|
|
49846
50486
|
gcls = (score2) => `cds-g-${gradeOf(score2).toLowerCase()}`;
|
|
49847
|
-
chip2 = (tone, text3) => `<span class="cds-chip ${tone}">${
|
|
49848
|
-
statTile2 = (value, label2, tone = "") => `<div class="cds-stat"><div class="big ${tone}">${
|
|
49849
|
-
callout2 = (inner, opts) => `<div class="cds-callout ${opts.quote ? "quote" : ""} ${opts.tone ?? ""}">${opts.title ? `<span class="ttl">${
|
|
50487
|
+
chip2 = (tone, text3) => `<span class="cds-chip ${tone}">${esc5(text3)}</span>`;
|
|
50488
|
+
statTile2 = (value, label2, tone = "") => `<div class="cds-stat"><div class="big ${tone}">${esc5(String(value))}</div><div class="lbl">${esc5(label2)}</div></div>`;
|
|
50489
|
+
callout2 = (inner, opts) => `<div class="cds-callout ${opts.quote ? "quote" : ""} ${opts.tone ?? ""}">${opts.title ? `<span class="ttl">${esc5(opts.title)}</span>` : ""}${inner}${opts.cite ? `<cite>${esc5(opts.cite)}</cite>` : ""}</div>`;
|
|
49850
50490
|
OUTCOME2 = { ran: ["ok", "Ran"], branch: ["ok", "Path taken"], did_nothing: ["warn", "Did nothing"], waiting: ["wait", "Waiting"], failed: ["bad", "Failed, holding"], unclear: ["warn", "Unclear"] };
|
|
49851
50491
|
FIX_STATE = {
|
|
49852
50492
|
applied: ["ok", "Fixed"],
|
|
@@ -50116,7 +50756,7 @@ function planText(kind, nodes, offer, ctx) {
|
|
|
50116
50756
|
if (kind === "reword_blank") {
|
|
50117
50757
|
const parts = blankParts(seen);
|
|
50118
50758
|
if (!parts) return { ok: false, refusal: "The blank part could not be matched to the step's wording, so nothing was changed. Reword it in GoHighLevel." };
|
|
50119
|
-
pattern = new RegExp(`(?<![\\p{L}\\p{N}])${
|
|
50759
|
+
pattern = new RegExp(`(?<![\\p{L}\\p{N}])${esc6(parts.word)}${GAP}*(?:\\{\\{[^{}]*\\}\\}${GAP}*)+${esc6(parts.punct)}`, "gu");
|
|
50120
50760
|
if (input === void 0 || input.trim() === "") {
|
|
50121
50761
|
const proposal = blankProposal(parts.word, parts.punct);
|
|
50122
50762
|
if (!proposal) {
|
|
@@ -50145,7 +50785,7 @@ function planText(kind, nodes, offer, ctx) {
|
|
|
50145
50785
|
if (!ok) return { ok: false, refusal: "Give a full web address that starts with https://, with no spaces." };
|
|
50146
50786
|
if (next === seen) return { ok: false, refusal: "That is the same address that did not open." };
|
|
50147
50787
|
const variants = [.../* @__PURE__ */ new Set([seen, seen.replace(/&/g, "&")])].sort((x, y) => y.length - x.length);
|
|
50148
|
-
pattern = new RegExp(variants.map(
|
|
50788
|
+
pattern = new RegExp(variants.map(esc6).join("|"), "g");
|
|
50149
50789
|
replacement = next;
|
|
50150
50790
|
} else {
|
|
50151
50791
|
if (!seen) return { ok: false, refusal: "There is nothing recorded to replace, so nothing was changed." };
|
|
@@ -50153,7 +50793,7 @@ function planText(kind, nodes, offer, ctx) {
|
|
|
50153
50793
|
if (kind === "replace_business" && !next) return { ok: false, refusal: "Type the business name that should appear." };
|
|
50154
50794
|
if (next === seen) return { ok: false, refusal: "That is the same as what is there now." };
|
|
50155
50795
|
const variants = [.../* @__PURE__ */ new Set([seen, escHtml(seen)])].sort((x, y) => y.length - x.length);
|
|
50156
|
-
pattern = new RegExp(variants.map(
|
|
50796
|
+
pattern = new RegExp(variants.map(esc6).join("|"), "g");
|
|
50157
50797
|
replacement = next;
|
|
50158
50798
|
}
|
|
50159
50799
|
const after = nodes.map((n) => n);
|
|
@@ -50507,11 +51147,11 @@ function verifyReadBack(sent, readBack, added, checks) {
|
|
|
50507
51147
|
}
|
|
50508
51148
|
return { held: problems.length === 0, problems };
|
|
50509
51149
|
}
|
|
50510
|
-
var
|
|
51150
|
+
var import_node_crypto23, import_zod77, ActionSchema3, asWorkflowActions, asObj10, asArr6, str12, NEEDS_A_PERSON, sha2, templatesHash2, find, clone, escHtml, readable, GAP, esc6, refs2, BRANCHING, MAX_STEPS, pretty, CORE;
|
|
50511
51151
|
var init_fixes = __esm({
|
|
50512
51152
|
"src/command-os/shopper/fixes.ts"() {
|
|
50513
51153
|
"use strict";
|
|
50514
|
-
|
|
51154
|
+
import_node_crypto23 = __toESM(require("node:crypto"));
|
|
50515
51155
|
import_zod77 = require("zod");
|
|
50516
51156
|
init_workflow_builder_client();
|
|
50517
51157
|
init_links2();
|
|
@@ -50521,7 +51161,7 @@ var init_fixes = __esm({
|
|
|
50521
51161
|
asArr6 = (v) => Array.isArray(v) ? v : [];
|
|
50522
51162
|
str12 = (v) => typeof v === "string" ? v : void 0;
|
|
50523
51163
|
NEEDS_A_PERSON = "This one needs a decision a button cannot make, so change it in GoHighLevel.";
|
|
50524
|
-
sha2 = (s) =>
|
|
51164
|
+
sha2 = (s) => import_node_crypto23.default.createHash("sha256").update(s).digest("hex");
|
|
50525
51165
|
templatesHash2 = (templates) => sha2(stableJson2(templates));
|
|
50526
51166
|
find = (nodes, offer, types) => {
|
|
50527
51167
|
if (offer.stepId) {
|
|
@@ -50537,7 +51177,7 @@ var init_fixes = __esm({
|
|
|
50537
51177
|
escHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
50538
51178
|
readable = (v, html2) => html2 ? htmlToText(v) : v;
|
|
50539
51179
|
GAP = "(?:[ \\t\\u00a0]| )";
|
|
50540
|
-
|
|
51180
|
+
esc6 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50541
51181
|
refs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : typeof v === "string" ? [v] : [];
|
|
50542
51182
|
BRANCHING = /* @__PURE__ */ new Set(["if_else", "find_opportunity", "transition", "goto", "fb_interactive_messenger", "find_contact"]);
|
|
50543
51183
|
MAX_STEPS = 40;
|
|
@@ -50573,21 +51213,21 @@ function parkedPlain(contacts) {
|
|
|
50573
51213
|
}
|
|
50574
51214
|
function ledgerPath(locationId2, runId, base) {
|
|
50575
51215
|
if (!SAFE_ID2.test(locationId2) || !SAFE_ID2.test(runId)) throw new Error("That client or run id is not valid.");
|
|
50576
|
-
return
|
|
51216
|
+
return import_node_path10.default.join(shopperDir(base), locationId2, "fixes", `${runId}.json`);
|
|
50577
51217
|
}
|
|
50578
51218
|
function readLedger(locationId2, runId, base) {
|
|
50579
51219
|
try {
|
|
50580
|
-
return asArr7(JSON.parse(
|
|
51220
|
+
return asArr7(JSON.parse(import_node_fs11.default.readFileSync(ledgerPath(locationId2, runId, base), "utf8")));
|
|
50581
51221
|
} catch {
|
|
50582
51222
|
return [];
|
|
50583
51223
|
}
|
|
50584
51224
|
}
|
|
50585
51225
|
function writeLedger(locationId2, runId, records, base) {
|
|
50586
51226
|
const file3 = ledgerPath(locationId2, runId, base);
|
|
50587
|
-
|
|
51227
|
+
import_node_fs11.default.mkdirSync(import_node_path10.default.dirname(file3), { recursive: true, mode: 448 });
|
|
50588
51228
|
const tmp = `${file3}.${process.pid}.${Date.now()}.tmp`;
|
|
50589
|
-
|
|
50590
|
-
|
|
51229
|
+
import_node_fs11.default.writeFileSync(tmp, JSON.stringify(records, null, 1), { mode: 384 });
|
|
51230
|
+
import_node_fs11.default.renameSync(tmp, file3);
|
|
50591
51231
|
}
|
|
50592
51232
|
function putRecord(locationId2, runId, rec2, base) {
|
|
50593
51233
|
const all = readLedger(locationId2, runId, base).filter((r) => r.id !== rec2.id);
|
|
@@ -50663,7 +51303,7 @@ async function previewFix(deps, run, key2, input) {
|
|
|
50663
51303
|
pipelines = void 0;
|
|
50664
51304
|
}
|
|
50665
51305
|
}
|
|
50666
|
-
const planned = planFix(offer.kind, before, offer, { seen: offer.seen, input, pipelines, newId: deps.newId ?? (() =>
|
|
51306
|
+
const planned = planFix(offer.kind, before, offer, { seen: offer.seen, input, pipelines, newId: deps.newId ?? (() => import_node_crypto24.default.randomUUID()) });
|
|
50667
51307
|
if (!planned.ok) return { ok: false, error: planned.refusal, ...planned.needsInput ? { needsInput: planned.needsInput } : {} };
|
|
50668
51308
|
const c = planned.change;
|
|
50669
51309
|
const lists = await baselineLists(deps, offer.workflowId, wf, c.after);
|
|
@@ -50761,7 +51401,7 @@ async function approveFix(deps, run, hash, confirmedWording) {
|
|
|
50761
51401
|
if (p.undoOf && (!undone || !undoable(undone, readLedger(run.locationId, run.runId, deps.base)))) return { ok: false, written: false, error: `That fix cannot be undone automatically any more. Nothing was changed. ${NO_AUTO_ROLLBACK}` };
|
|
50762
51402
|
const rec2 = {
|
|
50763
51403
|
...deps.actor ? { actor: deps.actor } : {},
|
|
50764
|
-
id:
|
|
51404
|
+
id: import_node_crypto24.default.randomUUID(),
|
|
50765
51405
|
seq: Math.max(0, ...readLedger(run.locationId, run.runId, deps.base).map((x) => x.seq ?? 0)) + 1,
|
|
50766
51406
|
key: p.key,
|
|
50767
51407
|
kind: p.kind,
|
|
@@ -50832,13 +51472,13 @@ async function approveFix(deps, run, hash, confirmedWording) {
|
|
|
50832
51472
|
writing.delete(p.workflowId);
|
|
50833
51473
|
}
|
|
50834
51474
|
}
|
|
50835
|
-
var
|
|
51475
|
+
var import_node_fs11, import_node_path10, import_node_crypto24, asObj11, asArr7, str13, PREVIEW_TTL_MS2, previews, writing, SAFE_ID2, newestFirst, NO_AUTO_ROLLBACK, templatesOf2, firstLine2, view;
|
|
50836
51476
|
var init_fix_apply = __esm({
|
|
50837
51477
|
"src/command-os/shopper/fix-apply.ts"() {
|
|
50838
51478
|
"use strict";
|
|
50839
|
-
|
|
50840
|
-
|
|
50841
|
-
|
|
51479
|
+
import_node_fs11 = __toESM(require("node:fs"));
|
|
51480
|
+
import_node_path10 = __toESM(require("node:path"));
|
|
51481
|
+
import_node_crypto24 = __toESM(require("node:crypto"));
|
|
50842
51482
|
init_workflow_builder_client();
|
|
50843
51483
|
init_workflow_publish_gate();
|
|
50844
51484
|
init_run();
|
|
@@ -51116,7 +51756,7 @@ main.brief{display:grid;gap:var(--space-5);align-content:start;min-width:0}
|
|
|
51116
51756
|
` })}${cockpitHeaderHtml("brief")}
|
|
51117
51757
|
<main class="brief">
|
|
51118
51758
|
<div class="cds-page-head">
|
|
51119
|
-
<div class="txt"><div class="cds-kicker">Daily brief${clientName ? ` \xB7 ${
|
|
51759
|
+
<div class="txt"><div class="cds-kicker">Daily brief${clientName ? ` \xB7 ${esc7(clientName)}` : ""}</div><h1 class="cds-page-title" id="brief-day">Today</h1><p class="cds-page-sub">What needs you today, then money, leads and each account, from what Command OS last read.</p><div class="cds-age" data-age="brief" role="status"><span class="cds-age-text small muted" id="brief-age"></span></div></div>
|
|
51120
51760
|
<button type="button" class="cds-btn" id="refresh-brief">Refresh brief</button>
|
|
51121
51761
|
</div>
|
|
51122
51762
|
<div id="tiles"></div>
|
|
@@ -51437,14 +52077,14 @@ if(PRO&&!CLIENT){const root=document.getElementById("brief-content");root.replac
|
|
|
51437
52077
|
else(async()=>{let got;try{got=await read(BRIEF_URL)}catch(e){const root=document.getElementById("brief-content");root.replaceChildren();const box=el("div","","cds-callout bad");box.setAttribute("role","status");box.appendChild(el("span","The brief could not be read","ttl"));box.appendChild(document.createTextNode("Check your license and connection, then press Refresh brief."));root.appendChild(box);return}try{show(got)}finally{askStale()}})();
|
|
51438
52078
|
</script></body></html>`;
|
|
51439
52079
|
}
|
|
51440
|
-
var
|
|
52080
|
+
var esc7;
|
|
51441
52081
|
var init_brief_page = __esm({
|
|
51442
52082
|
"src/command-os/brief-page.ts"() {
|
|
51443
52083
|
"use strict";
|
|
51444
52084
|
init_cockpit_shell();
|
|
51445
52085
|
init_script_json();
|
|
51446
52086
|
init_cockpit_cache();
|
|
51447
|
-
|
|
52087
|
+
esc7 = (t) => t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
51448
52088
|
}
|
|
51449
52089
|
});
|
|
51450
52090
|
|
|
@@ -51662,7 +52302,7 @@ function cardHtml(c) {
|
|
|
51662
52302
|
const entry = CONNECTOR_REGISTRY.find((entry2) => entry2.id === c.id);
|
|
51663
52303
|
if (!entry) return "";
|
|
51664
52304
|
if (c.state === "not-built") return `<section class="cds-panel connection" id="${c.id}">${head(c, `<span class="cds-chip">Not built yet</span>`)}<p>Coming later. Nothing to do now.</p></section>`;
|
|
51665
|
-
if (c.state === "via-claude") return `<section class="cds-panel connection" id="${c.id}">${head(c, `<span class="cds-chip info">Through your Claude</span>`)}<p>${
|
|
52305
|
+
if (c.state === "via-claude") return `<section class="cds-panel connection" id="${c.id}">${head(c, `<span class="cds-chip info">Through your Claude</span>`)}<p>${esc8(entry.gives)}</p>${how(connectorInstructions(entry, "html"), c.id, false)}</section>`;
|
|
51666
52306
|
const proven = c.state === "proven";
|
|
51667
52307
|
const google = c.id === "google-calendar";
|
|
51668
52308
|
const needsSetup = google && c.setup === "needs-setup";
|
|
@@ -51670,20 +52310,20 @@ function cardHtml(c) {
|
|
|
51670
52310
|
const state2 = chip3(c, needs);
|
|
51671
52311
|
return `<section class="cds-panel connection" id="${c.id}" data-system="${c.id}">
|
|
51672
52312
|
${head(c, `<span class="cds-chip state-chip${state2.tone ? " " + state2.tone : ""}"${needs ? ' data-needs="1"' : ""}>${state2.word}</span>`)}
|
|
51673
|
-
<p>${
|
|
52313
|
+
<p>${esc8(entry.gives)}</p>
|
|
51674
52314
|
${statusKv()}
|
|
51675
|
-
<p class="next-step"${nextStep(c) ? "" : " hidden"}>${
|
|
52315
|
+
<p class="next-step"${nextStep(c) ? "" : " hidden"}>${esc8(nextStep(c))}</p>
|
|
51676
52316
|
${how(needsSetup ? "" : connectorInstructions(entry, "html"), c.id, needsSetup)}
|
|
51677
|
-
${google ? `<p class="setup-notice cds-callout warn"${needsSetup ? "" : " hidden"}>${
|
|
52317
|
+
${google ? `<p class="setup-notice cds-callout warn"${needsSetup ? "" : " hidden"}>${esc8(CALENDAR_SETUP_MESSAGE)}</p>` : ""}
|
|
51678
52318
|
<div class="action-controls cds-stack">
|
|
51679
52319
|
<div class="cds-row"><button class="replace-toggle cds-btn ghost" type="button" aria-controls="connect-${c.id}" aria-expanded="false"${proven && !needsSetup ? "" : " hidden"}>${google ? "Reconnect" : "Replace key"}</button>
|
|
51680
52320
|
<button class="disconnect-toggle cds-btn ghost" type="button" aria-controls="disconnect-${c.id}" aria-expanded="false"${c.connectedAt ? "" : " hidden"}>Disconnect</button></div>
|
|
51681
|
-
${google ? `<div class="actions cds-row" id="connect-${c.id}"${proven || needsSetup ? " hidden" : ""}><button class="cds-btn ghost" type="button" data-action="start">Connect</button><button class="cds-btn ghost" type="button" data-action="finish">Finish</button></div>` : `<form class="connect-form cds-stack" id="connect-${c.id}" autocomplete="off"${proven ? " hidden" : ""}>${c.id === "zoom" ? zoomIdFields(c.id) : ""}<div class="cds-field"><label for="key-${c.id}">Paste ${c.id === "zoom" ? "Client Secret" : keyWord(c.id)}</label><input class="cds-input" id="key-${c.id}" name="key" type="password" minlength="8" required autocomplete="new-password"></div><div class="cds-field"><label for="label-${c.id}">Account label (optional)</label><input class="cds-input" id="label-${c.id}" name="label" value="${
|
|
52321
|
+
${google ? `<div class="actions cds-row" id="connect-${c.id}"${proven || needsSetup ? " hidden" : ""}><button class="cds-btn ghost" type="button" data-action="start">Connect</button><button class="cds-btn ghost" type="button" data-action="finish">Finish</button></div>` : `<form class="connect-form cds-stack" id="connect-${c.id}" autocomplete="off"${proven ? " hidden" : ""}>${c.id === "zoom" ? zoomIdFields(c.id) : ""}<div class="cds-field"><label for="key-${c.id}">Paste ${c.id === "zoom" ? "Client Secret" : keyWord(c.id)}</label><input class="cds-input" id="key-${c.id}" name="key" type="password" minlength="8" required autocomplete="new-password"></div><div class="cds-field"><label for="label-${c.id}">Account label (optional)</label><input class="cds-input" id="label-${c.id}" name="label" value="${esc8(c.label || "")}"></div><div class="cds-row"><button class="cds-btn ghost" type="submit">Connect</button></div></form>`}
|
|
51682
52322
|
<form class="disconnect-form cds-stack" id="disconnect-${c.id}" hidden><div class="cds-field"><label for="confirm-${c.id}">Type DISCONNECT to remove this connection</label><input class="cds-input" id="confirm-${c.id}" name="confirm" autocomplete="off" required pattern="DISCONNECT"></div><div class="cds-row"><button class="cds-btn ghost" type="submit">Disconnect</button></div></form>
|
|
51683
52323
|
</div>
|
|
51684
52324
|
<p class="result cds-callout" role="status" aria-live="polite"></p><a class="sign-in cds-link" hidden target="_blank" rel="noopener noreferrer">Open Google sign-in</a>
|
|
51685
52325
|
${keyDetails(c)}
|
|
51686
|
-
${google ? `<details class="operator-details cds-details"${needsSetup ? "" : " hidden"}><summary>Details</summary><p>${
|
|
52326
|
+
${google ? `<details class="operator-details cds-details"${needsSetup ? "" : " hidden"}><summary>Details</summary><p>${esc8(c.setupDetail || "")}</p></details>` : ""}
|
|
51687
52327
|
</section>`;
|
|
51688
52328
|
}
|
|
51689
52329
|
function connectionsPageHtml(cards, seatToken = "") {
|
|
@@ -51703,8 +52343,8 @@ main.connections{display:grid;gap:var(--space-6)}
|
|
|
51703
52343
|
<section class="cds-panel needs-box"><div class="cds-panel-head"><div class="txt"><div class="cds-panel-label">Needs your hand</div><h2 class="cds-panel-title">Needs you today</h2></div></div><ul class="cds-list" id="needs">${cards.filter(needsYou).map((c) => needsRow(c, c.readError ? "Check the unreadable connection" : "Reconnect to prove access")).join("")}</ul><div class="cds-empty" id="no-needs"${cards.some(needsYou) ? " hidden" : ""}><p>No connection needs your attention.</p></div><p class="also-connect" id="also-connect"${alsoConnect(cards) ? "" : " hidden"}>${alsoConnect(cards)}</p></section>
|
|
51704
52344
|
<section class="cds-panel"><div class="cds-panel-head"><div class="txt"><div class="cds-panel-label">Saved on this computer</div><h2 class="cds-panel-title">What these states mean</h2></div></div>
|
|
51705
52345
|
<p>Connected means we checked the key works with that service when you saved it. Not proven means a key is saved but that check has not passed yet. Refresh reads what is saved here again; it does not ask each service.</p>
|
|
51706
|
-
<dl class="cds-kv states-kv"><dt>Last read</dt><dd id="loaded">Reading now</dd><dt>Could be read</dt><dd id="honesty">${unreadable.length ? `Could not read: ${
|
|
51707
|
-
<nav class="cds-jump" aria-label="Jump to system">${cards.map((c) => `<a href="#${c.id}">${
|
|
52346
|
+
<dl class="cds-kv states-kv"><dt>Last read</dt><dd id="loaded">Reading now</dd><dt>Could be read</dt><dd id="honesty">${unreadable.length ? `Could not read: ${esc8(unreadable.join(", "))}.` : "All saved connections could be read."}</dd></dl></section>
|
|
52347
|
+
<nav class="cds-jump" aria-label="Jump to system">${cards.map((c) => `<a href="#${c.id}">${esc8(c.name)}</a>`).join("")}</nav>
|
|
51708
52348
|
<div class="systems">${cards.map(cardHtml).join("")}</div></main>${cockpitShellClose()}
|
|
51709
52349
|
<script>
|
|
51710
52350
|
${COCKPIT_TYPE_JS}
|
|
@@ -51831,7 +52471,7 @@ refresh();
|
|
|
51831
52471
|
})();
|
|
51832
52472
|
</script></body></html>`;
|
|
51833
52473
|
}
|
|
51834
|
-
var
|
|
52474
|
+
var esc8, needsYou, optional, progressText, alsoConnect, statusKv, keyDetails, keyWord, nextStep, chip3, head, how, zoomIdFields, needsRow;
|
|
51835
52475
|
var init_page2 = __esm({
|
|
51836
52476
|
"src/command-os/connectors/page.ts"() {
|
|
51837
52477
|
"use strict";
|
|
@@ -51839,25 +52479,25 @@ var init_page2 = __esm({
|
|
|
51839
52479
|
init_script_json();
|
|
51840
52480
|
init_instructions();
|
|
51841
52481
|
init_registry();
|
|
51842
|
-
|
|
52482
|
+
esc8 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
51843
52483
|
needsYou = (c) => c.builtState === "built" && (Boolean(c.readError) || c.state === "saved-unproven");
|
|
51844
52484
|
optional = (c) => c.builtState === "built" && !c.readError && c.state === "not-connected";
|
|
51845
52485
|
progressText = (cards) => `${cards.filter((c) => c.state === "proven").length} of the ${cards.filter((c) => c.builtState === "built").length} systems you connect on this page are connected`;
|
|
51846
52486
|
alsoConnect = (cards) => {
|
|
51847
|
-
const links = cards.filter(optional).map((c) => `<a class="cds-link" href="#${c.id}">${
|
|
52487
|
+
const links = cards.filter(optional).map((c) => `<a class="cds-link" href="#${c.id}">${esc8(c.name)}</a>`);
|
|
51848
52488
|
if (!links.length) return "";
|
|
51849
52489
|
const list4 = links.length > 1 ? `${links.slice(0, -1).join(", ")} and ${links[links.length - 1]}` : links[0];
|
|
51850
52490
|
return `You can also connect ${list4} when you want ${links.length > 1 ? "them" : "it"} in your brief.`;
|
|
51851
52491
|
};
|
|
51852
52492
|
statusKv = () => `<dl class="cds-kv status" hidden></dl>`;
|
|
51853
|
-
keyDetails = (c) => `<details class="key-details cds-details"${c.keyEndsWith ? "" : " hidden"}><summary>Details</summary><dl class="cds-kv key-kv"><dt>Key ending</dt><dd>${
|
|
52493
|
+
keyDetails = (c) => `<details class="key-details cds-details"${c.keyEndsWith ? "" : " hidden"}><summary>Details</summary><dl class="cds-kv key-kv"><dt>Key ending</dt><dd>${esc8(c.keyEndsWith || "")}</dd></dl></details>`;
|
|
51854
52494
|
keyWord = (id2) => id2 === "stripe" ? "restricted key" : id2 === "zoom" ? "Account ID, Client ID and Client Secret" : id2 === "brain" ? "access token" : "bot token";
|
|
51855
52495
|
nextStep = (c) => c.state === "proven" || c.id === "google-calendar" && c.setup === "needs-setup" ? "" : c.id === "google-calendar" ? "Next: press Connect and sign in with Google." : c.state === "saved-unproven" ? `Next: paste the ${keyWord(c.id)} again and press Connect, so we can check it works.` : `Next: paste your ${keyWord(c.id)} below and press Connect.`;
|
|
51856
52496
|
chip3 = (c, needs) => c.readError ? { tone: "warn", word: "Could not read" } : c.state === "saved-unproven" ? { tone: "warn", word: "Not proven" } : c.state === "not-connected" ? { tone: "", word: "Not connected" } : needs ? { tone: "warn", word: "Needs you today" } : c.state === "proven" ? { tone: "ok", word: "Connected" } : { tone: "", word: "Not connected" };
|
|
51857
|
-
head = (c, chips) => `<div class="cds-panel-head"><div class="txt"><h2 class="cds-panel-title">${
|
|
52497
|
+
head = (c, chips) => `<div class="cds-panel-head"><div class="txt"><h2 class="cds-panel-title">${esc8(c.name)}</h2></div><div class="cds-row">${chips}</div></div>`;
|
|
51858
52498
|
how = (inner, id2, hidden2) => `<details class="how-it-works cds-details" id="instructions-${id2}"${hidden2 ? " hidden" : ""}><summary>How this connection works</summary><div class="connector-instructions">${inner}</div></details>`;
|
|
51859
52499
|
zoomIdFields = (id2) => `<div class="cds-field"><label for="accountId-${id2}">Paste Account ID</label><input class="cds-input" id="accountId-${id2}" name="accountId" required minlength="6" autocomplete="off" spellcheck="false"></div><div class="cds-field"><label for="clientId-${id2}">Paste Client ID</label><input class="cds-input" id="clientId-${id2}" name="clientId" type="password" required minlength="6" autocomplete="new-password"></div>`;
|
|
51860
|
-
needsRow = (c, ask) => `<li><a class="ttl" href="#${c.id}">${
|
|
52500
|
+
needsRow = (c, ask) => `<li><a class="ttl" href="#${c.id}">${esc8(c.name)}<span class="sub">${esc8(ask)}</span></a></li>`;
|
|
51861
52501
|
}
|
|
51862
52502
|
});
|
|
51863
52503
|
|
|
@@ -52154,7 +52794,7 @@ function isHex(value) {
|
|
|
52154
52794
|
}
|
|
52155
52795
|
function readAgencyBranding(base = appDataDir()) {
|
|
52156
52796
|
try {
|
|
52157
|
-
const st = JSON.parse((0,
|
|
52797
|
+
const st = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path11.join)(base, "cockpit-state.json"), "utf8"));
|
|
52158
52798
|
const out = {};
|
|
52159
52799
|
if (typeof st.agencyName === "string" && st.agencyName.trim()) out.agencyName = st.agencyName.trim();
|
|
52160
52800
|
if (isHex(st.agencyAccent)) out.accent = st.agencyAccent;
|
|
@@ -52171,12 +52811,12 @@ function resolveBranding(overrides = {}, base) {
|
|
|
52171
52811
|
const logoDataUri = isRasterDataUri(overrides.logoDataUri) ? overrides.logoDataUri : saved.logoDataUri;
|
|
52172
52812
|
return { ...agencyName && { agencyName }, accent, ...logoDataUri && { logoDataUri } };
|
|
52173
52813
|
}
|
|
52174
|
-
var
|
|
52814
|
+
var import_node_fs12, import_node_path11, DEFAULT_ACCENT;
|
|
52175
52815
|
var init_branding = __esm({
|
|
52176
52816
|
"src/command-os/branding.ts"() {
|
|
52177
52817
|
"use strict";
|
|
52178
|
-
|
|
52179
|
-
|
|
52818
|
+
import_node_fs12 = require("node:fs");
|
|
52819
|
+
import_node_path11 = require("node:path");
|
|
52180
52820
|
init_credentials_store();
|
|
52181
52821
|
init_svg_sanitize();
|
|
52182
52822
|
DEFAULT_ACCENT = "#B8934A";
|
|
@@ -52186,6 +52826,7 @@ var init_branding = __esm({
|
|
|
52186
52826
|
// src/command-os/client-status.ts
|
|
52187
52827
|
function clientStateOf(line4) {
|
|
52188
52828
|
if (line4.state === "CHECKED") return "in-place";
|
|
52829
|
+
if (line4.state === "DONE_UNCHECKED" && /^(Built as a draft and switched off|Left as a draft on purpose)/.test(line4.reason ?? "")) return "switched-off";
|
|
52189
52830
|
if (line4.state === "WAITING") return "waiting";
|
|
52190
52831
|
return "not-yet";
|
|
52191
52832
|
}
|
|
@@ -52239,6 +52880,10 @@ function clientNextSteps(items) {
|
|
|
52239
52880
|
};
|
|
52240
52881
|
for (const it of items) {
|
|
52241
52882
|
if (it.state === "in-place") continue;
|
|
52883
|
+
if (it.state === "switched-off") {
|
|
52884
|
+
add2(NEXT_STEP_RULES[1].step);
|
|
52885
|
+
continue;
|
|
52886
|
+
}
|
|
52242
52887
|
if (it.state === "waiting") {
|
|
52243
52888
|
const who2 = scrubForClient(it.resolvedBy ?? "");
|
|
52244
52889
|
if (who2) {
|
|
@@ -52255,8 +52900,15 @@ function clientHeadline(items) {
|
|
|
52255
52900
|
const live = items.filter((i) => i.state === "in-place").length;
|
|
52256
52901
|
const notYet = items.filter((i) => i.state === "not-yet").length;
|
|
52257
52902
|
const waiting = items.filter((i) => i.state === "waiting").length;
|
|
52903
|
+
const off = items.filter((i) => i.state === "switched-off").length;
|
|
52258
52904
|
const plural3 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
52259
52905
|
if (!items.length) return { headline: "Your account has not been checked yet", sub: "As soon as we run the first check, this page fills in on its own." };
|
|
52906
|
+
if (!notYet && !waiting && off) {
|
|
52907
|
+
return {
|
|
52908
|
+
headline: `Everything is built; ${plural3(off, "automation is", "automations are")} switched off until turned on`,
|
|
52909
|
+
sub: `${live ? `${plural3(live, "thing is", "things are")} live in your account and ` : ""}${plural3(off, "automation is", "automations are")} built and switched off. Everything below was read back from your live account.`
|
|
52910
|
+
};
|
|
52911
|
+
}
|
|
52260
52912
|
if (!notYet && !waiting) {
|
|
52261
52913
|
return {
|
|
52262
52914
|
headline: "Everything we set out to build is live",
|
|
@@ -52266,6 +52918,7 @@ function clientHeadline(items) {
|
|
|
52266
52918
|
const parts = [];
|
|
52267
52919
|
if (live) parts.push(`${plural3(live, "thing is", "things are")} live in your account`);
|
|
52268
52920
|
if (notYet) parts.push(`${plural3(notYet, "is", "are")} built but not confirmed yet`);
|
|
52921
|
+
if (off) parts.push(`${plural3(off, "is", "are")} built and switched off`);
|
|
52269
52922
|
if (waiting) parts.push(`${plural3(waiting, "is", "are")} waiting on someone else`);
|
|
52270
52923
|
return {
|
|
52271
52924
|
headline: notYet ? `${plural3(notYet, "item", "items")} still to confirm` : `${plural3(waiting, "item is", "items are")} waiting on someone else`,
|
|
@@ -52276,7 +52929,7 @@ function statusFromHandoff(saved, opts = {}) {
|
|
|
52276
52929
|
const items = saved.lines.map((l) => {
|
|
52277
52930
|
const written = l.plain?.trim() || (l.state === "CHECKED" ? plainFromEvidence(l.evidence ?? "") : l.state === "DONE_UNCHECKED" ? plainFromEvidence(l.reason ?? "") : "");
|
|
52278
52931
|
const state2 = clientStateOf(l);
|
|
52279
|
-
const fallback = state2 === "in-place" ? "This is in place in your account." : state2 === "waiting" ? l.resolvedBy || l.roughly ? "" : "This is with someone outside our control, and we are chasing it." : "This has been built but is not confirmed yet.";
|
|
52932
|
+
const fallback = state2 === "in-place" ? "This is in place in your account." : state2 === "waiting" ? l.resolvedBy || l.roughly ? "" : "This is with someone outside our control, and we are chasing it." : state2 === "switched-off" ? "This is built and switched off until it is turned on." : "This has been built but is not confirmed yet.";
|
|
52280
52933
|
const sentence = scrubForClient(written) || fallback;
|
|
52281
52934
|
return {
|
|
52282
52935
|
title: scrubForClient(clientTitle(l.name)) || clientTitle(l.name),
|
|
@@ -52333,9 +52986,9 @@ function statusPageTitle(input) {
|
|
|
52333
52986
|
return `${input.accountName}: what is live${input.agencyName ? ` \xB7 ${input.agencyName}` : ""}`;
|
|
52334
52987
|
}
|
|
52335
52988
|
function agencyDetailSections(d) {
|
|
52336
|
-
const list4 = (items) => items.length ? `<ul class="issues">${items.map((i) => `<li>${
|
|
52337
|
-
const block = (title2, intro, body) => `<h3 class="dh">${
|
|
52338
|
-
return `<section id="detail"><h2>Account build detail</h2><p class="intro">The technical record of this build. None of it appears on the client's page.</p>` + block("Your jobs in the account", "What the agency still does, in the words Verify wrote them, click paths included.", list4(d.jobs)) + block("Workflows", "Each automation in the plan and what the latest check found.", list4(d.workflows.map((w) => `${w.name}: ${w.status}`))) + block("Tags", `${d.tags.length} in the plan.`, d.tags.length ? `<p>${
|
|
52989
|
+
const list4 = (items) => items.length ? `<ul class="issues">${items.map((i) => `<li>${esc9(i)}</li>`).join("")}</ul>` : `<p class="intro">None recorded.</p>`;
|
|
52990
|
+
const block = (title2, intro, body) => `<h3 class="dh">${esc9(title2)}</h3><p class="intro">${esc9(intro)}</p>${body}`;
|
|
52991
|
+
return `<section id="detail"><h2>Account build detail</h2><p class="intro">The technical record of this build. None of it appears on the client's page.</p>` + block("Your jobs in the account", "What the agency still does, in the words Verify wrote them, click paths included.", list4(d.jobs)) + block("Workflows", "Each automation in the plan and what the latest check found.", list4(d.workflows.map((w) => `${w.name}: ${w.status}`))) + block("Tags", `${d.tags.length} in the plan.`, d.tags.length ? `<p>${esc9(d.tags.join(" \xB7 "))}</p>` : list4([])) + block("Custom fields", `${d.fields.length} in the plan.`, list4(d.fields)) + block("Custom values", `${d.customValues.length} in the plan.`, list4(d.customValues)) + block("Record ids", "Plan references and the GoHighLevel ids they were built as.", list4(d.ids.map((x) => `${x.ref}: ${x.id}`))) + `</section>`;
|
|
52339
52992
|
}
|
|
52340
52993
|
function renderAgencyDetailHtml(input, detail) {
|
|
52341
52994
|
return renderClientStatusHtml(input, { agencyDetail: detail });
|
|
@@ -52350,23 +53003,24 @@ function renderClientStatusHtml(input, opts = {}) {
|
|
|
52350
53003
|
const live = input.items.filter((i) => i.state === "in-place");
|
|
52351
53004
|
const notYet = input.items.filter((i) => i.state === "not-yet");
|
|
52352
53005
|
const waiting = input.items.filter((i) => i.state === "waiting");
|
|
53006
|
+
const off = input.items.filter((i) => i.state === "switched-off");
|
|
52353
53007
|
const next = input.nextSteps?.length ? input.nextSteps : clientNextSteps(input.items);
|
|
52354
53008
|
const stamp = friendlyStamp(input.checkedAt, input.timeZone);
|
|
52355
53009
|
const day = friendlyDate(input.checkedAt, input.timeZone);
|
|
52356
53010
|
const latest = scrubLatestCheck(input.latestCheck);
|
|
52357
|
-
const latestHtml = latest ? `<section id="latest"><h2>What the latest check found</h2>${latest.summary ? `<p class="intro">${
|
|
53011
|
+
const latestHtml = latest ? `<section id="latest"><h2>What the latest check found</h2>${latest.summary ? `<p class="intro">${esc9(latest.summary)}</p>` : ""}${latest.issues?.length ? `<ul class="issues">${latest.issues.map((i) => `<li>${esc9(i)}</li>`).join("")}</ul>` : `<p class="intro">Nothing needed your attention this time.</p>`}</section>` : "";
|
|
52358
53012
|
const entry = (it) => {
|
|
52359
53013
|
const cls = it.state === "in-place" ? "ok" : it.state === "waiting" ? "wait" : "soon";
|
|
52360
|
-
const detail2 = it.state === "waiting" && (it.resolvedBy || it.roughly) ? `<p class="who"><b>Who sorts it:</b> ${
|
|
52361
|
-
const body = it.sentence ? `<p>${
|
|
52362
|
-
return `<article class="item ${cls}"><span class="pill">${
|
|
53014
|
+
const detail2 = it.state === "waiting" && (it.resolvedBy || it.roughly) ? `<p class="who"><b>Who sorts it:</b> ${esc9(scrubForClient(it.resolvedBy ?? "") || "not stated")}.${it.roughly ? ` <b>Roughly when:</b> ${esc9(scrubForClient(it.roughly))}.` : ""}</p>` : "";
|
|
53015
|
+
const body = it.sentence ? `<p>${esc9(it.sentence)}</p>` : "";
|
|
53016
|
+
return `<article class="item ${cls}"><span class="pill">${esc9(CLIENT_STATE_LABEL[it.state])}</span><h3>${esc9(it.title)}</h3>${body}${detail2}</article>`;
|
|
52363
53017
|
};
|
|
52364
|
-
const section2 = (id2, title2, intro, list4) => list4.length ? `<section id="${id2}"><h2>${
|
|
53018
|
+
const section2 = (id2, title2, intro, list4) => list4.length ? `<section id="${id2}"><h2>${esc9(title2)}</h2><p class="intro">${esc9(intro)}</p>${list4.map(entry).join("\n")}</section>` : "";
|
|
52365
53019
|
const footLeft = `${agency ? `Prepared by ${agency} for ` : "Prepared for "}${input.accountName} \xB7 last checked ${day}`;
|
|
52366
53020
|
const revLine = input.revision && input.revision > 0 ? `Update ${input.revision} \xB7 last checked ${stamp}` : `Last checked ${stamp}`;
|
|
52367
53021
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
|
|
52368
|
-
<title>${
|
|
52369
|
-
<meta name="description" content="${
|
|
53022
|
+
<title>${esc9(detail ? `Account detail, for your agency: ${input.accountName}` : statusPageTitle(input))}</title>
|
|
53023
|
+
<meta name="description" content="${esc9(`What is live in the ${input.accountName} account right now.`)}">
|
|
52370
53024
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
52371
53025
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=DM+Sans:wght@400;500;700&display=swap">
|
|
52372
53026
|
<style>
|
|
@@ -52477,7 +53131,7 @@ function renderClientStatusHtml(input, opts = {}) {
|
|
|
52477
53131
|
}
|
|
52478
53132
|
</style></head><body>
|
|
52479
53133
|
<div class="chrome"><div class="chrome-in">
|
|
52480
|
-
<span class="who">${
|
|
53134
|
+
<span class="who">${esc9(agency || input.accountName)}</span>
|
|
52481
53135
|
<nav>${detail ? '<a href="#detail">Build detail</a>' : ""}${latestHtml ? '<a href="#latest">Latest check</a>' : ""}${live.length ? '<a href="#live">Live now</a>' : ""}${notYet.length ? '<a href="#soon">To confirm</a>' : ""}${waiting.length ? '<a href="#waiting">Waiting</a>' : ""}${next.length ? '<a href="#next">What happens next</a>' : ""}</nav>
|
|
52482
53136
|
<div class="tools">
|
|
52483
53137
|
<button type="button" id="print">Print / save PDF</button>
|
|
@@ -52486,24 +53140,26 @@ function renderClientStatusHtml(input, opts = {}) {
|
|
|
52486
53140
|
</div>
|
|
52487
53141
|
</div></div>
|
|
52488
53142
|
<div class="page"><div class="sheet">
|
|
52489
|
-
${agency || isRasterDataUri(input.logoDataUri) ? `<div class="brand">${isRasterDataUri(input.logoDataUri) ? `<img src="${
|
|
53143
|
+
${agency || isRasterDataUri(input.logoDataUri) ? `<div class="brand">${isRasterDataUri(input.logoDataUri) ? `<img src="${esc9(input.logoDataUri)}" alt="${esc9(agency ?? "")}">` : ""}${agency ? `<span class="agency">${esc9(agency)}</span>` : ""}<span class="for">Prepared for<br><b>${esc9(input.accountName)}</b></span></div>` : ""}
|
|
52490
53144
|
<p class="eyebrow">${detail ? "Account detail, for your agency" : "What is live in your account"}</p>
|
|
52491
|
-
<h1>${
|
|
52492
|
-
<p class="stamp">${
|
|
52493
|
-
<div class="verdict"><b>${
|
|
53145
|
+
<h1>${esc9(input.accountName)}</h1>
|
|
53146
|
+
<p class="stamp">${esc9(revLine)}</p>
|
|
53147
|
+
<div class="verdict"><b>${esc9(headline3)}</b><span>${esc9(sub)}</span></div>
|
|
52494
53148
|
${detail ? `<p class="living">This page is for your agency only and is never sent to the client. The client receives the short handoff page; everything technical is kept here, rewritten every time the account is checked.</p>` : ""}<p class="living">This page checks itself. Every time your account is re-checked, this page is rewritten at the same address, so what you see here is what is true today. Bookmark it and come back whenever you want to know where things stand.</p>
|
|
52495
53149
|
<div class="tiles">
|
|
52496
53150
|
<div class="tile ok"><div class="n">${live.length}</div><div class="l">Live now</div></div>
|
|
52497
53151
|
<div class="tile soon"><div class="n">${notYet.length}</div><div class="l">To confirm</div></div>
|
|
52498
|
-
<div class="tile wait"><div class="n">${waiting.length}</div><div class="l">Waiting on others</div></div
|
|
53152
|
+
<div class="tile wait"><div class="n">${waiting.length}</div><div class="l">Waiting on others</div></div>${off.length ? `
|
|
53153
|
+
<div class="tile soon"><div class="n">${off.length}</div><div class="l">Switched off</div></div>` : ""}
|
|
52499
53154
|
</div>
|
|
52500
53155
|
${latestHtml}
|
|
52501
53156
|
${section2("live", "Live in your account", "Each of these was read back from your account, not assumed.", live)}
|
|
53157
|
+
${section2("off", "Built, switched off", "These are in your account and switched off on purpose. Nothing sends from them until they are turned on.", off)}
|
|
52502
53158
|
${section2("soon", "Built, not confirmed yet", "The work is done. These are the ones we have not been able to confirm for you yet, and why.", notYet)}
|
|
52503
53159
|
${section2("waiting", "Waiting on someone else", "Outside our hands. Here is who sorts each one, and roughly when.", waiting)}
|
|
52504
|
-
${next.length ? `<section id="next"><h2>What happens next</h2><ol>${next.map((n) => `<li>${
|
|
53160
|
+
${next.length ? `<section id="next"><h2>What happens next</h2><ol>${next.map((n) => `<li>${esc9(n)}</li>`).join("")}</ol></section>` : ""}
|
|
52505
53161
|
${detail ? agencyDetailSections(detail) : ""}
|
|
52506
|
-
<div class="foot"><span>${
|
|
53162
|
+
<div class="foot"><span>${esc9(footLeft)}</span>${input.contact ? `<span>Questions? ${input.contact.href ? `<a href="${esc9(input.contact.href)}">${esc9(input.contact.label)}</a>` : esc9(input.contact.label)}</span>` : ""}</div>
|
|
52507
53163
|
</div></div>
|
|
52508
53164
|
<script>
|
|
52509
53165
|
(function(){
|
|
@@ -52530,7 +53186,7 @@ ${detail ? agencyDetailSections(detail) : ""}
|
|
|
52530
53186
|
</script>
|
|
52531
53187
|
</body></html>`;
|
|
52532
53188
|
}
|
|
52533
|
-
var CLIENT_STATE_LABEL, CLIENT_TITLE_RULES, DE_JARGON, LEAK_RULES, NEXT_STEP_RULES, toHex2,
|
|
53189
|
+
var CLIENT_STATE_LABEL, CLIENT_TITLE_RULES, DE_JARGON, LEAK_RULES, NEXT_STEP_RULES, toHex2, esc9;
|
|
52534
53190
|
var init_client_status = __esm({
|
|
52535
53191
|
"src/command-os/client-status.ts"() {
|
|
52536
53192
|
"use strict";
|
|
@@ -52542,7 +53198,8 @@ var init_client_status = __esm({
|
|
|
52542
53198
|
CLIENT_STATE_LABEL = {
|
|
52543
53199
|
"in-place": "Live in your account",
|
|
52544
53200
|
"not-yet": "Not confirmed yet",
|
|
52545
|
-
waiting: "Waiting on someone else"
|
|
53201
|
+
waiting: "Waiting on someone else",
|
|
53202
|
+
"switched-off": "Built, switched off"
|
|
52546
53203
|
};
|
|
52547
53204
|
CLIENT_TITLE_RULES = [
|
|
52548
53205
|
{ re: /^intake form exists/i, title: "Your inquiry form" },
|
|
@@ -52564,8 +53221,10 @@ var init_client_status = __esm({
|
|
|
52564
53221
|
DE_JARGON = [
|
|
52565
53222
|
[/\boauth\b(?:\s+(?:calendar\s+)?connection\b)?/gi, "calendar connection"],
|
|
52566
53223
|
[/\b(?:a2p(?:[\s/]+10dlc)?|10dlc)\b(?:\s+registration\b)?/gi, "text-messaging registration"],
|
|
52567
|
-
|
|
52568
|
-
[
|
|
53224
|
+
// Never inside a menu path: "Automation > Workflows" is the name on the screen (OS re-check, 2026-10-30).
|
|
53225
|
+
[/(?<!>\s*)\bworkflow(s)?\b/gi, "automation$1"],
|
|
53226
|
+
// Never inside a menu path: "Settings > Custom Values" is the name on the screen (OS re-check, 2026-10-30).
|
|
53227
|
+
[/(?<!>\s*)\bcustom value(s)?\b/gi, "saved detail$1"],
|
|
52569
53228
|
[/\bread[- ]back\b/gi, "check"],
|
|
52570
53229
|
[/\bsub[- ]account\b/gi, "account"],
|
|
52571
53230
|
[/\bin the GHL UI\b/gi, "in your account"],
|
|
@@ -52594,7 +53253,7 @@ var init_client_status = __esm({
|
|
|
52594
53253
|
{ re: /handoff call|on your own|without help/i, step: "On the handover call, do one task in the account yourself. That is what confirms you can run it without us." }
|
|
52595
53254
|
];
|
|
52596
53255
|
toHex2 = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
52597
|
-
|
|
53256
|
+
esc9 = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
52598
53257
|
}
|
|
52599
53258
|
});
|
|
52600
53259
|
|
|
@@ -52728,206 +53387,6 @@ var init_client_status_placement = __esm({
|
|
|
52728
53387
|
}
|
|
52729
53388
|
});
|
|
52730
53389
|
|
|
52731
|
-
// src/build-contract.ts
|
|
52732
|
-
function contentHash2(value) {
|
|
52733
|
-
const canonical5 = (v) => Array.isArray(v) ? v.map(canonical5) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => [k, canonical5(v[k])])) : v;
|
|
52734
|
-
return (0, import_node_crypto24.createHash)("sha256").update(JSON.stringify(canonical5(value)) ?? "null").digest("hex");
|
|
52735
|
-
}
|
|
52736
|
-
function staffListHash(roster) {
|
|
52737
|
-
return contentHash2({ lines: (roster?.lines ?? "").trim(), notifyName: roster?.notifyName ?? "", callsName: roster?.callsName ?? "" });
|
|
52738
|
-
}
|
|
52739
|
-
function assertStaffUnchanged(approval, roster) {
|
|
52740
|
-
if ((approval?.staffHash ?? staffListHash(null)) !== staffListHash(roster)) throw new Error(STAFF_CHANGED);
|
|
52741
|
-
}
|
|
52742
|
-
function assertBuildApproval(approval, inputHash) {
|
|
52743
|
-
if (!approval?.at) throw new Error("Approve building before running Build.");
|
|
52744
|
-
if (!approval.inputHash || inputHash && approval.inputHash !== inputHash) throw new Error(INTAKE_CHANGED);
|
|
52745
|
-
}
|
|
52746
|
-
function assertApprovedPlan(saved, approval, inputHash, build) {
|
|
52747
|
-
if (!saved?.plan || !saved.contentHash && !build?.inputHash) throw new Error("Run Build before verifying.");
|
|
52748
|
-
assertBuildApproval(approval, inputHash);
|
|
52749
|
-
if (saved.contentHash && contentHash2(saved.plan) !== saved.contentHash) throw new Error("The saved build plan changed. Run Build again before verifying.");
|
|
52750
|
-
const builtInput = build?.inputHash ?? saved.inputHash;
|
|
52751
|
-
if (!builtInput || builtInput !== approval.inputHash || inputHash && builtInput !== inputHash || saved.inputHash && saved.inputHash !== builtInput) throw new Error(INTAKE_CHANGED);
|
|
52752
|
-
}
|
|
52753
|
-
function readPlanApproval(locationId2, base = appDataDir()) {
|
|
52754
|
-
try {
|
|
52755
|
-
return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path10.join)(base, "cockpit-state.json"), "utf8")).planApprovals?.[locationId2];
|
|
52756
|
-
} catch {
|
|
52757
|
-
return void 0;
|
|
52758
|
-
}
|
|
52759
|
-
}
|
|
52760
|
-
function stageFreshness(inputHash, saved, approval, build, verify2, brief) {
|
|
52761
|
-
let from = 7;
|
|
52762
|
-
let reason3 = "";
|
|
52763
|
-
const builtInput = build?.inputHash ?? saved?.inputHash;
|
|
52764
|
-
const buildChanged = Boolean(builtInput && builtInput !== inputHash);
|
|
52765
|
-
const buildReason = "The intake changed since Build. Run Build and Verify again.";
|
|
52766
|
-
const planHash = saved ? saved.contentHash ?? contentHash2(saved.plan) : void 0;
|
|
52767
|
-
const reviewedInput = brief?.inputHash ?? approval?.inputHash ?? saved?.inputHash;
|
|
52768
|
-
if (reviewedInput && reviewedInput !== inputHash) {
|
|
52769
|
-
from = 1;
|
|
52770
|
-
reason3 = INTAKE_CHANGED;
|
|
52771
|
-
} else if (approval && approval.inputHash !== inputHash) {
|
|
52772
|
-
from = 2;
|
|
52773
|
-
reason3 = "The intake review changed. Approve building again.";
|
|
52774
|
-
} else if (buildChanged) {
|
|
52775
|
-
from = 3;
|
|
52776
|
-
reason3 = buildReason;
|
|
52777
|
-
} else if (verify2 && (!planHash || contentHash2(saved.plan) !== planHash || verify2.inputHash !== planHash || !build?.hash || verify2.buildHash !== build.hash)) {
|
|
52778
|
-
from = 4;
|
|
52779
|
-
reason3 = "The build changed after this check. Run Verify again.";
|
|
52780
|
-
}
|
|
52781
|
-
return Array.from({ length: Math.max(0, 7 - from) }, (_, i) => ({ stage: from + i, status: "stale", reason: buildChanged && from + i >= 3 ? buildReason : reason3 }));
|
|
52782
|
-
}
|
|
52783
|
-
function planDependencies(plan) {
|
|
52784
|
-
const result = [];
|
|
52785
|
-
for (const section2 of ["users", "pipelines", "customFields", "fields", "tags", "customValues", "calendars", "forms", "workflows"]) {
|
|
52786
|
-
for (const item of plan?.[section2] ?? []) result.push({ ref: item.ref || `${section2}:${item.name}`, name: item.name || item.title || [item.firstName, item.lastName].filter(Boolean).join(" ") || item.ref, instruction: `Check ${item.name || item.ref} against the approved plan.` });
|
|
52787
|
-
}
|
|
52788
|
-
for (const h of plan?.handoffs ?? []) result.push({ ref: h.ref, name: h.title || h.name || h.ref, instruction: h.instruction });
|
|
52789
|
-
return result;
|
|
52790
|
-
}
|
|
52791
|
-
function normalizedIntake(snap, draft) {
|
|
52792
|
-
return draft?.savedAt && (!snap.answeredAt || draft.savedAt > snap.answeredAt) ? { ...snap.answers, ...draft.answers } : snap.answers ?? draft?.answers ?? {};
|
|
52793
|
-
}
|
|
52794
|
-
function intakeInput(answers, industry, questions) {
|
|
52795
|
-
return contentHash2({ answers, industry, questions });
|
|
52796
|
-
}
|
|
52797
|
-
function shortenPlanLine(text3, limit = 360) {
|
|
52798
|
-
if (text3.length <= limit) return text3;
|
|
52799
|
-
const tokens2 = text3.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
|
|
52800
|
-
let end = "";
|
|
52801
|
-
for (const token2 of tokens2) {
|
|
52802
|
-
const next = end ? `${end} ${token2}` : token2;
|
|
52803
|
-
if (next.length > limit - 1) {
|
|
52804
|
-
if (!end) return token2.length === text3.length ? text3 : `${token2}\u2026`;
|
|
52805
|
-
break;
|
|
52806
|
-
}
|
|
52807
|
-
end = next;
|
|
52808
|
-
}
|
|
52809
|
-
return `${end}\u2026`;
|
|
52810
|
-
}
|
|
52811
|
-
function executablePlanLines(saved) {
|
|
52812
|
-
const p = saved.plan;
|
|
52813
|
-
const preview = saved.preview;
|
|
52814
|
-
const lines = [];
|
|
52815
|
-
const kinds = {
|
|
52816
|
-
users: "staff member",
|
|
52817
|
-
pipelines: "pipeline",
|
|
52818
|
-
stages: "pipeline stage",
|
|
52819
|
-
customFields: "field",
|
|
52820
|
-
fields: "field",
|
|
52821
|
-
tags: "tag",
|
|
52822
|
-
customValues: "custom value",
|
|
52823
|
-
calendars: "calendar",
|
|
52824
|
-
forms: "form",
|
|
52825
|
-
funnels: "funnel",
|
|
52826
|
-
workflows: "workflow",
|
|
52827
|
-
emails: "email message",
|
|
52828
|
-
sms: "text message",
|
|
52829
|
-
handoffs: "handoff",
|
|
52830
|
-
actions: "workflow step",
|
|
52831
|
-
user: "staff member",
|
|
52832
|
-
stage: "pipeline stage",
|
|
52833
|
-
cv: "custom value",
|
|
52834
|
-
email: "email message",
|
|
52835
|
-
email_template: "email template",
|
|
52836
|
-
sms_template: "text message template",
|
|
52837
|
-
pipeline: "pipeline",
|
|
52838
|
-
field: "field",
|
|
52839
|
-
tag: "tag",
|
|
52840
|
-
workflow: "workflow",
|
|
52841
|
-
form: "form",
|
|
52842
|
-
calendar: "calendar",
|
|
52843
|
-
handoff: "handoff",
|
|
52844
|
-
funnel: "funnel"
|
|
52845
|
-
};
|
|
52846
|
-
const words2 = (s) => s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_.-]+/g, " ").trim();
|
|
52847
|
-
const kindOf2 = (s) => kinds[s] || words2(s).replace(/s$/, "") || "plan object";
|
|
52848
|
-
const names = /* @__PURE__ */ new Map();
|
|
52849
|
-
const objects = /* @__PURE__ */ new Map();
|
|
52850
|
-
const label2 = (x, kind) => x.name?.trim() || x.title?.trim() || [x.firstName, x.lastName].filter(Boolean).join(" ") || `Unnamed ${kind}`;
|
|
52851
|
-
const collect = (value, kind = "plan object") => {
|
|
52852
|
-
if (Array.isArray(value)) {
|
|
52853
|
-
for (const item of value) collect(item, kind);
|
|
52854
|
-
return;
|
|
52855
|
-
}
|
|
52856
|
-
if (!value || typeof value !== "object") return;
|
|
52857
|
-
if (typeof value.ref === "string") {
|
|
52858
|
-
names.set(value.ref, label2(value, kind));
|
|
52859
|
-
objects.set(value.ref, value);
|
|
52860
|
-
}
|
|
52861
|
-
for (const [key2, child] of Object.entries(value)) collect(child, key2 === "templates" ? "message template" : kinds[key2] || kind);
|
|
52862
|
-
};
|
|
52863
|
-
collect(p);
|
|
52864
|
-
for (const kind of ["emails", "sms"]) for (const t of p?.templates?.[kind] ?? []) names.set(t.ref, label2(t, kind === "emails" ? "email template" : "text message template"));
|
|
52865
|
-
const escapeRE = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
52866
|
-
const refs3 = [...names.keys()].sort((a, b) => b.length - a.length);
|
|
52867
|
-
const refPattern = refs3.length ? new RegExp(`(?<![\\w./:@])(?:${refs3.map(escapeRE).join("|")})(?![\\w]|\\.[\\w])`, "g") : void 0;
|
|
52868
|
-
const missingRefPattern = /(?<![\w./:@])(user|pipeline|stage|field|tag|workflow|form|calendar|email_template|sms_template|email|sms|cv|handoff|funnel)\.[a-z0-9_]+(?![\w]|\.[\w])/g;
|
|
52869
|
-
const resolve15 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
|
|
52870
|
-
const renderRefs = (s) => resolve15(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
|
|
52871
|
-
const mergeLabel = (field2) => {
|
|
52872
|
-
const [group, ...parts] = field2.trim().split(".");
|
|
52873
|
-
const key2 = parts.join(".");
|
|
52874
|
-
const candidates = group === "custom_values" ? p?.customValues : group === "contact" ? [...p?.customFields ?? [], ...p?.fields ?? []] : [];
|
|
52875
|
-
const slug3 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
52876
|
-
const match = (candidates ?? []).find((x) => [x.key, x.fieldKey, x.ref, x.name].some((v) => typeof v === "string" && (v === field2 || slug3(v.split(".").pop()) === slug3(key2))));
|
|
52877
|
-
if (match?.name) return match.name;
|
|
52878
|
-
if (group === "contact") return `contact's ${key2 === "name" ? "full name" : words2(key2)}`;
|
|
52879
|
-
if (group === "custom_values") return words2(key2);
|
|
52880
|
-
return words2(field2);
|
|
52881
|
-
};
|
|
52882
|
-
const plain = (s) => renderRefs(s.replace(/<a\b[^>]*href\s*=\s*(["'])(.*?)\1[^>]*>([\s\S]*?)<\/a>/gi, (_all, _quote, href, text3) => `${text3} (${href})`).replace(/<[^>]*>/g, " ").replace(/&(?:amp|nbsp|quot|apos|lt|gt|mdash|ndash);|&#(?:x[0-9a-f]+|[0-9]+);/gi, (entity) => {
|
|
52883
|
-
const named = { "&": "&", " ": " ", """: '"', "'": "'", "<": "<", ">": ">", "—": " ", "–": " " };
|
|
52884
|
-
if (named[entity.toLowerCase()] !== void 0) return named[entity.toLowerCase()];
|
|
52885
|
-
const code = entity[2].toLowerCase() === "x" ? parseInt(entity.slice(3), 16) : parseInt(entity.slice(2), 10);
|
|
52886
|
-
return code <= 1114111 ? String.fromCodePoint(code) : entity;
|
|
52887
|
-
}).replace(/{{\s*([^{}]+?)\s*}}/g, (_all, field2) => `[${mergeLabel(field2)}]`)).replace(/\bMCP\b/g, "GHL Command").replace(/Agency OS/g, "Command OS").replace(/\s*—\s*/g, " ").replace(/\s+/g, " ").replace(/\s+([.,;!?])/g, "$1").trim();
|
|
52888
|
-
const FIELD_KIND = { TEXT: "text", LARGE_TEXT: "long text", NUMERICAL: "number", MONETORY: "money", MONETARY: "money", DATE: "date", SINGLE_OPTIONS: "one choice from a list", MULTIPLE_OPTIONS: "several choices from a list", CHECKBOX: "checkboxes", RADIO: "one choice", PHONE: "phone number", EMAIL: "email address", FILE_UPLOAD: "file upload", TEXTBOX_LIST: "list of text boxes", SIGNATURE: "signature" };
|
|
52889
|
-
const HIDDEN_KEYS = /* @__PURE__ */ new Set(["ref", "position", "order", "sortOrder"]);
|
|
52890
|
-
const keyWords = (k) => k === "dataType" ? "kind" : words2(k.replace(/Refs?$/, ""));
|
|
52891
|
-
const show = (v, key2 = "") => typeof v === "string" ? names.get(v) || (/Refs?$/.test(key2) ? `Unnamed ${kindOf2(key2.replace(/Refs?$/, "").replace(/^teamMember$/, "user").replace(/^template$/, "message template"))}` : key2 === "dataType" ? FIELD_KIND[v] ?? words2(v).toLowerCase() : plain(key2 === "mergeTags" ? `{{${v}}}` : key2 === "type" ? words2(v) : v)) : Array.isArray(v) ? v.map((value) => show(value, key2)).join(", ") : v && typeof v === "object" ? ((e) => e.length === 1 && e[0][0] === "name" ? show(e[0][1], "name") : e.map(([k, value]) => `${keyWords(k)}: ${show(value, k)}`).join("; "))(Object.entries(v).filter(([k]) => !HIDDEN_KEYS.has(k))) : String(v ?? "");
|
|
52892
|
-
const sentence = (s) => {
|
|
52893
|
-
const text3 = plain(s);
|
|
52894
|
-
return text3 && !/[.!?…]$/.test(text3) ? `${text3}.` : text3;
|
|
52895
|
-
};
|
|
52896
|
-
const messageCopy = (t) => [t.subject ? `Subject: ${sentence(t.subject)}` : "", t.body || t.html ? `Message: ${sentence(t.body || t.html)}` : t.bodyOutline ? `Message outline: ${sentence(t.bodyOutline)}` : ""].filter(Boolean).join(" ");
|
|
52897
|
-
for (const section2 of ["users", "pipelines", "customFields", "fields", "tags", "customValues", "calendars", "forms", "funnels"]) for (const x of p?.[section2] ?? []) {
|
|
52898
|
-
const existing = preview?.items?.find((i) => i.ref === x.ref)?.status === "existing";
|
|
52899
|
-
lines.push(`${existing ? "Reuse" : "Create"} ${kindOf2(section2)}: ${label2(x, kindOf2(section2))}. ${show(Object.fromEntries(Object.entries(x).filter(([k]) => !["ref", "name"].includes(k))))}`);
|
|
52900
|
-
}
|
|
52901
|
-
for (const w of p?.workflows ?? []) {
|
|
52902
|
-
lines.push(`Workflow: ${label2(w, "workflow")}. ${show(w.trigger || w.triggers || "")}`);
|
|
52903
|
-
for (const [i, a] of (w.actions ?? []).entries()) {
|
|
52904
|
-
const attrs = a.attributes || a;
|
|
52905
|
-
const recipient = attrs.to || attrs.userRef || attrs.assignedTo || attrs.notification?.selectedUser || (/email|sms/.test(a.type || "") ? "The contact in this workflow" : /notification|assign_user|task/.test(a.type || "") ? "not set yet (choose who gets this)" : "");
|
|
52906
|
-
const asset = objects.get(a.emailRef || a.smsRef || attrs.emailRef || attrs.smsRef);
|
|
52907
|
-
const message = objects.get(a.templateRef || attrs.templateRef || asset?.templateRef) || asset;
|
|
52908
|
-
const actionName = a.name || words2(a.type || a.action || "Workflow step").replace(/^./, (c) => c.toUpperCase());
|
|
52909
|
-
lines.push(`${i + 1}. ${actionName}.${recipient ? ` Recipient: ${show(recipient, attrs.userRef ? "userRef" : "")}.` : ""} ${sentence(show(attrs))}${message ? ` ${messageCopy(message)}` : ""}`);
|
|
52910
|
-
}
|
|
52911
|
-
}
|
|
52912
|
-
for (const kind of ["emails", "sms"]) for (const t of p?.templates?.[kind] ?? []) lines.push(`Message template: ${label2(t, kind === "emails" ? "email template" : "text message template")}. Recipient: The contact in the workflow using this template. ${messageCopy(t)}`);
|
|
52913
|
-
for (const d of saved.dependencies ?? planDependencies(p)) lines.push(`Dependency: ${names.get(d.ref) || (d.name && d.name !== d.ref ? renderRefs(d.name) : `Unnamed ${kinds[d.ref.split(".")[0]] || "plan object"}`)}. ${d.instruction || "Needs evidence from Verify or a visible manual task."}`);
|
|
52914
|
-
for (const h of preview?.handoffs ?? []) if (!h.met) lines.push(`Unresolved: ${h.title || names.get(h.ref) || "Unnamed handoff"}. ${h.instruction || ""}`);
|
|
52915
|
-
for (const w of preview?.workflows ?? []) for (const m of [...w.manual ?? [], ...w.needsContent ?? []]) lines.push(`Unresolved: ${w.name || names.get(w.ref) || "Unnamed workflow"}. ${m.reason}`);
|
|
52916
|
-
return lines.map(sentence);
|
|
52917
|
-
}
|
|
52918
|
-
var import_node_crypto24, import_node_fs11, import_node_path10, STAFF_CHANGED, INTAKE_CHANGED;
|
|
52919
|
-
var init_build_contract = __esm({
|
|
52920
|
-
"src/build-contract.ts"() {
|
|
52921
|
-
"use strict";
|
|
52922
|
-
import_node_crypto24 = require("node:crypto");
|
|
52923
|
-
import_node_fs11 = require("node:fs");
|
|
52924
|
-
import_node_path10 = require("node:path");
|
|
52925
|
-
init_credentials_store();
|
|
52926
|
-
STAFF_CHANGED = "Your staff list changed after you approved the build. Review and approve again.";
|
|
52927
|
-
INTAKE_CHANGED = "The intake changed. Run Brief again and approve the new review.";
|
|
52928
|
-
}
|
|
52929
|
-
});
|
|
52930
|
-
|
|
52931
53390
|
// src/command-os/approved-status-writer.ts
|
|
52932
53391
|
async function approvedStatusWriter(registry2, locationId2) {
|
|
52933
53392
|
const record = readStatusRecord(locationId2);
|
|
@@ -53041,7 +53500,7 @@ async function prepareHandoffEmail(client, locationId2, name2, email, url) {
|
|
|
53041
53500
|
const found = await client.get("/contacts/", { params: { locationId: locationId2, query: email, limit: 5 } });
|
|
53042
53501
|
const contact = found.contacts?.find((c) => c.email?.toLowerCase() === email.toLowerCase());
|
|
53043
53502
|
if (!contact) throw new Error("Add this email as a contact in the client account, then review the handoff email again.");
|
|
53044
|
-
const body = { locationId: locationId2, contactId: contact.id, email, subject: `Your ${name2} handoff`, html: `<p>Your account handoff lists what was delivered and what still needs attention.</p><p><a href="${
|
|
53503
|
+
const body = { locationId: locationId2, contactId: contact.id, email, subject: `Your ${name2} handoff`, html: `<p>Your account handoff lists what was delivered and what still needs attention.</p><p><a href="${esc10(url)}">Open your handoff</a></p>` };
|
|
53045
53504
|
return { ...body, hash: contentHash2(body) };
|
|
53046
53505
|
}
|
|
53047
53506
|
async function sendHandoffEmail(client, preview, confirmation) {
|
|
@@ -53052,12 +53511,12 @@ async function sendHandoffEmail(client, preview, confirmation) {
|
|
|
53052
53511
|
const sent = await client.post("/conversations/messages", { body: { type: "Email", contactId: preview.contactId, emailTo: preview.email, subject: preview.subject, html: preview.html }, noRetry: true });
|
|
53053
53512
|
return { accepted: Boolean(sent.messageId), messageId: sent.messageId, note: sent.messageId ? "The email was accepted by GoHighLevel. Client receipt has not been verified." : "The send result is uncertain. Check Conversations before trying again." };
|
|
53054
53513
|
}
|
|
53055
|
-
var
|
|
53514
|
+
var esc10;
|
|
53056
53515
|
var init_handoff_delivery = __esm({
|
|
53057
53516
|
"src/command-os/handoff-delivery.ts"() {
|
|
53058
53517
|
"use strict";
|
|
53059
53518
|
init_build_contract();
|
|
53060
|
-
|
|
53519
|
+
esc10 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
53061
53520
|
}
|
|
53062
53521
|
});
|
|
53063
53522
|
|
|
@@ -55826,7 +56285,8 @@ function stepsOf(ctx, plan) {
|
|
|
55826
56285
|
if (f("review_requests")) steps.push(reviewWords(ctx.review).step);
|
|
55827
56286
|
const after = [
|
|
55828
56287
|
f("nurture") ? "Leads who do not book get a month of helpful messages." : "",
|
|
55829
|
-
|
|
56288
|
+
// OS re-check (2026-10-30): Win-back reaches leads who go cold from now on, never an existing list.
|
|
56289
|
+
f("winback") ? "Leads who go cold from now on get a win-back message." : "",
|
|
55830
56290
|
f("quote_followup") ? ctx.programFollowUp ? PROGRAM_WORDS.quotes.after : "Every quote is followed up until they answer." : "",
|
|
55831
56291
|
hasWorkflow(plan, "workflow.reply_route") ? "Anyone who replies is handed to a person straight away." : ""
|
|
55832
56292
|
].filter(Boolean).join(" ");
|
|
@@ -55957,25 +56417,25 @@ function ownerPageTitle(o) {
|
|
|
55957
56417
|
function renderOwnerHandoffHtml(o, colours, stamp) {
|
|
55958
56418
|
const agency = o.agency.name;
|
|
55959
56419
|
const prepared = `Prepared for ${o.accountName} \xB7 ${o.dateLabel}`;
|
|
55960
|
-
const table = (head4, rows2) => `<table><thead><tr>${head4.map((h) => `<th>${
|
|
56420
|
+
const table = (head4, rows2) => `<table><thead><tr>${head4.map((h) => `<th>${esc11(h)}</th>`).join("")}</tr></thead><tbody>${rows2.map((r) => `<tr>${r.map((c) => `<td>${esc11(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
|
55961
56421
|
const sections = [];
|
|
55962
56422
|
if (o.problems.length) {
|
|
55963
|
-
sections.push(`<section class="blk"><h3>What you told us, and what we built for it</h3>${table(["The problem you hired us for", "What we built", "How you will see it working"], o.problems.map((p) => [p.problem, p.built, p.see]))}${o.problemSource ? `<p class="note">${
|
|
56423
|
+
sections.push(`<section class="blk"><h3>What you told us, and what we built for it</h3>${table(["The problem you hired us for", "What we built", "How you will see it working"], o.problems.map((p) => [p.problem, p.built, p.see]))}${o.problemSource ? `<p class="note">${esc11(o.problemSource)}</p>` : ""}</section>`);
|
|
55964
56424
|
}
|
|
55965
56425
|
if (o.steps.length) {
|
|
55966
|
-
sections.push(`<section class="blk"><h3>What happens to a new lead</h3><div class="steps">${o.steps.map((s) => `<div class="step"><b>${
|
|
56426
|
+
sections.push(`<section class="blk"><h3>What happens to a new lead</h3><div class="steps">${o.steps.map((s) => `<div class="step"><b>${esc11(s.title)}</b>${esc11(s.text)}</div>`).join("")}</div>${o.afterSteps ? `<p>${esc11(o.afterSteps)}</p>` : ""}</section>`);
|
|
55967
56427
|
} else if (o.afterSteps) {
|
|
55968
|
-
sections.push(`<section class="blk"><h3>What happens to a new lead</h3><p>${
|
|
56428
|
+
sections.push(`<section class="blk"><h3>What happens to a new lead</h3><p>${esc11(o.afterSteps)}</p></section>`);
|
|
55969
56429
|
}
|
|
55970
56430
|
if (o.team.length) sections.push(`<section class="blk"><h3>Your team</h3>${table(["Person", "What they receive"], o.team.map((t) => [t.person, t.receives]))}</section>`);
|
|
55971
|
-
if (o.stages.length) sections.push(`<section class="blk"><h3>Your sales stages</h3><div class="stages">${o.stages.map((s) => `<span>${
|
|
56431
|
+
if (o.stages.length) sections.push(`<section class="blk"><h3>Your sales stages</h3><div class="stages">${o.stages.map((s) => `<span>${esc11(s)}</span>`).join("")}</div></section>`);
|
|
55972
56432
|
if (o.bought.working.length || o.bought.next.length) {
|
|
55973
|
-
sections.push(`<section class="blk"><h3>What you bought</h3><table class="bought"><thead><tr><th>${
|
|
56433
|
+
sections.push(`<section class="blk"><h3>What you bought</h3><table class="bought"><thead><tr><th>${esc11(o.bought.workingLabel)}</th><th>Being set up next</th></tr></thead><tbody><tr><td>${esc11(o.bought.working.join(" \xB7 ") || "Nothing yet")}</td><td>${esc11(o.bought.next.join(" \xB7 ") || "Nothing else")}</td></tr></tbody></table></section>`);
|
|
55974
56434
|
}
|
|
55975
|
-
sections.push(`<section class="blk"><h3>Before we switch it on, from you</h3>${o.requests.length ? `<ul class="check">${o.requests.map((r) => `<li>${
|
|
56435
|
+
sections.push(`<section class="blk"><h3>Before we switch it on, from you</h3>${o.requests.length ? `<ul class="check">${o.requests.map((r) => `<li>${esc11(r)}</li>`).join("")}</ul>` : `<p>${esc11(NOTHING_NEEDED)}</p>`}</section>`);
|
|
55976
56436
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
|
|
55977
|
-
<title>${
|
|
55978
|
-
<meta name="description" content="${
|
|
56437
|
+
<title>${esc11(ownerPageTitle(o))}</title>
|
|
56438
|
+
<meta name="description" content="${esc11(`What the new client system does for ${o.accountName}.`)}">
|
|
55979
56439
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
55980
56440
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;600;700;800&family=Playfair+Display:wght@700&display=swap">
|
|
55981
56441
|
<style>
|
|
@@ -56040,7 +56500,7 @@ function renderOwnerHandoffHtml(o, colours, stamp) {
|
|
|
56040
56500
|
}
|
|
56041
56501
|
</style></head><body>
|
|
56042
56502
|
<div class="chrome"><div class="chrome-in">
|
|
56043
|
-
<span class="who">${
|
|
56503
|
+
<span class="who">${esc11(agency || o.accountName)}</span>
|
|
56044
56504
|
<div class="tools">
|
|
56045
56505
|
<button type="button" id="print">Print / save PDF</button>
|
|
56046
56506
|
<button type="button" id="theme" aria-label="Switch between light and dark">Dark</button>
|
|
@@ -56048,11 +56508,11 @@ function renderOwnerHandoffHtml(o, colours, stamp) {
|
|
|
56048
56508
|
</div>
|
|
56049
56509
|
</div></div>
|
|
56050
56510
|
<div class="page"><main class="paper">
|
|
56051
|
-
<div class="brand"><span class="agency">${isRasterDataUri(o.agency.logoDataUri) ? `<img src="${
|
|
56511
|
+
<div class="brand"><span class="agency">${isRasterDataUri(o.agency.logoDataUri) ? `<img src="${esc11(o.agency.logoDataUri)}" alt="">` : ""}${agency ? esc11(agency) : ""}</span><span>${esc11(prepared)}</span></div>
|
|
56052
56512
|
<h2>Your new client system</h2>
|
|
56053
|
-
<p class="lead">${
|
|
56513
|
+
<p class="lead">${esc11(o.summary)}</p>
|
|
56054
56514
|
${sections.join("\n")}
|
|
56055
|
-
<div class="foot"><p>${
|
|
56515
|
+
<div class="foot"><p>${esc11([ownerContactLine(o), OWNER_UPDATES_LINE].filter(Boolean).join(" "))}</p>${stamp ? `<p class="stamp">${esc11(stamp)}</p>` : ""}</div>
|
|
56056
56516
|
</main></div>
|
|
56057
56517
|
<script>
|
|
56058
56518
|
(function(){
|
|
@@ -56079,7 +56539,7 @@ ${sections.join("\n")}
|
|
|
56079
56539
|
</script>
|
|
56080
56540
|
</body></html>`;
|
|
56081
56541
|
}
|
|
56082
|
-
var OWNER_UPDATES_LINE, MAX_OWNER_REQUESTS, MAX_PROBLEM_ROWS, MAX_TEAM_ROWS, MAX_STAGE_CHIPS, MAX_CALENDARS_NAMED, NOTHING_NEEDED, PROBLEM, BUILT_FOR, SEE, PROGRAM_WORDS, programWords, UNIT_MINUTES, NUMBER_WORDS, countWords, clean4, cap, sameName, FINDING_TO_BOUGHT, GAP_RULES, gapId, OWNER_REQUESTS, NON_US_ZONE_WORDS, US_PLACE_WORDS, US_TIME_ZONE_WORDS,
|
|
56542
|
+
var OWNER_UPDATES_LINE, MAX_OWNER_REQUESTS, MAX_PROBLEM_ROWS, MAX_TEAM_ROWS, MAX_STAGE_CHIPS, MAX_CALENDARS_NAMED, NOTHING_NEEDED, PROBLEM, BUILT_FOR, SEE, PROGRAM_WORDS, programWords, UNIT_MINUTES, NUMBER_WORDS, countWords, clean4, cap, sameName, FINDING_TO_BOUGHT, GAP_RULES, gapId, OWNER_REQUESTS, NON_US_ZONE_WORDS, US_PLACE_WORDS, US_TIME_ZONE_WORDS, esc11;
|
|
56083
56543
|
var init_owner_handoff = __esm({
|
|
56084
56544
|
"src/command-os/owner-handoff.ts"() {
|
|
56085
56545
|
"use strict";
|
|
@@ -56121,7 +56581,9 @@ var init_owner_handoff = __esm({
|
|
|
56121
56581
|
missed: "A text back to every call you miss",
|
|
56122
56582
|
quotes: "Follow-up on every quote until they answer",
|
|
56123
56583
|
noshow: "Booking confirmations, reminders before the visit, and a friendly reschedule when someone misses it",
|
|
56124
|
-
|
|
56584
|
+
// OS re-check (2026-10-30): Win-back starts from a tag Lead Nurture adds, so it reaches leads who go cold from now on,
|
|
56585
|
+
// never an existing list; the client page said it messaged the old list.
|
|
56586
|
+
reactivation: "A win-back message to leads who go cold from now on",
|
|
56125
56587
|
nurture: "A month of helpful messages that stop when they reply",
|
|
56126
56588
|
reviews: "A review request, with your link",
|
|
56127
56589
|
pipeline: "Sales stages that track every lead"
|
|
@@ -56131,7 +56593,7 @@ var init_owner_handoff = __esm({
|
|
|
56131
56593
|
missed: "A text going out to every call you miss",
|
|
56132
56594
|
quotes: "Follow-ups going out on every open quote",
|
|
56133
56595
|
noshow: "Reminders going out before every visit",
|
|
56134
|
-
reactivation: "Win-back messages going out to
|
|
56596
|
+
reactivation: "Win-back messages going out to leads who go cold",
|
|
56135
56597
|
nurture: "Helpful messages going out to leads who have not booked",
|
|
56136
56598
|
recall: "Rebooking reminders going out on schedule",
|
|
56137
56599
|
deposits: "A deposit taken at each booking",
|
|
@@ -56203,7 +56665,7 @@ var init_owner_handoff = __esm({
|
|
|
56203
56665
|
{ re: /\b(mountain|denver|mst|mdt|mt)\b/, zone: "America/Denver" },
|
|
56204
56666
|
{ re: /\b(pacific|los angeles|pst|pdt|pt)\b/, zone: "America/Los_Angeles" }
|
|
56205
56667
|
];
|
|
56206
|
-
|
|
56668
|
+
esc11 = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
56207
56669
|
}
|
|
56208
56670
|
});
|
|
56209
56671
|
|
|
@@ -56335,7 +56797,7 @@ function composeStatusPageSections(input, opts = {}) {
|
|
|
56335
56797
|
});
|
|
56336
56798
|
sections.push(hero);
|
|
56337
56799
|
names.push("hero");
|
|
56338
|
-
const listHtml = (xs, empty) => `<p>${(xs.length ? xs : [empty]).map(
|
|
56800
|
+
const listHtml = (xs, empty) => `<p>${(xs.length ? xs : [empty]).map(esc12).join(" \xB7 ")}</p>`;
|
|
56339
56801
|
sections.push(buildValueCards({
|
|
56340
56802
|
cards: [
|
|
56341
56803
|
{ title: owner.bought.workingLabel, bodyHtml: listHtml(owner.bought.working, "Nothing yet"), iconName: CARD_ICONS.working },
|
|
@@ -56375,7 +56837,7 @@ function composeStatusPageSections(input, opts = {}) {
|
|
|
56375
56837
|
envelope = applyTheme(envelope, theme);
|
|
56376
56838
|
return { envelope, report: { sections: names, itemsShown: faqItems.length, warnings } };
|
|
56377
56839
|
}
|
|
56378
|
-
function
|
|
56840
|
+
function esc12(s) {
|
|
56379
56841
|
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
56380
56842
|
}
|
|
56381
56843
|
async function publishStatusPageToGhl(input, placement, writer, opts = {}) {
|
|
@@ -56717,7 +57179,7 @@ var init_client_status_publish = __esm({
|
|
|
56717
57179
|
// src/workflow-repair-approval.ts
|
|
56718
57180
|
function readWorkflowRepairs(id2, base = appDataDir()) {
|
|
56719
57181
|
try {
|
|
56720
|
-
return JSON.parse((0,
|
|
57182
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(file(id2, base), "utf8"));
|
|
56721
57183
|
} catch {
|
|
56722
57184
|
return [];
|
|
56723
57185
|
}
|
|
@@ -56731,19 +57193,19 @@ function reviewWorkflowRepair(locationId2, workflowId, name2, before, after, bas
|
|
|
56731
57193
|
];
|
|
56732
57194
|
const repair = { workflowId, name: name2, beforeHash: contentHash2(before), afterHash: contentHash2(after), hash: contentHash2({ workflowId, before, after }), changes };
|
|
56733
57195
|
if (readPlanApproval(locationId2, base)?.repairHashes?.includes(repair.hash)) return true;
|
|
56734
|
-
(0,
|
|
56735
|
-
(0,
|
|
57196
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path12.join)(base, "workflow-repairs"), { recursive: true, mode: 448 });
|
|
57197
|
+
(0, import_node_fs13.writeFileSync)(file(locationId2, base), JSON.stringify([...readWorkflowRepairs(locationId2, base).filter((r) => r.workflowId !== workflowId), repair]), { mode: 384 });
|
|
56736
57198
|
return false;
|
|
56737
57199
|
}
|
|
56738
|
-
var
|
|
57200
|
+
var import_node_fs13, import_node_path12, file;
|
|
56739
57201
|
var init_workflow_repair_approval = __esm({
|
|
56740
57202
|
"src/workflow-repair-approval.ts"() {
|
|
56741
57203
|
"use strict";
|
|
56742
|
-
|
|
56743
|
-
|
|
57204
|
+
import_node_fs13 = require("node:fs");
|
|
57205
|
+
import_node_path12 = require("node:path");
|
|
56744
57206
|
init_credentials_store();
|
|
56745
57207
|
init_build_contract();
|
|
56746
|
-
file = (id2, base) => (0,
|
|
57208
|
+
file = (id2, base) => (0, import_node_path12.join)(base, "workflow-repairs", encodeURIComponent(id2) + ".json");
|
|
56747
57209
|
}
|
|
56748
57210
|
});
|
|
56749
57211
|
|
|
@@ -56758,6 +57220,25 @@ async function produceHandoffSnapshot(opts) {
|
|
|
56758
57220
|
const check = opts.checks?.find((c) => c.ref === d.ref);
|
|
56759
57221
|
const confirmed = check?.ok && check.evidence.trim();
|
|
56760
57222
|
if (confirmed) return { name: d.name, state: "CHECKED", evidence: check.evidence, plain: `${d.name} is in the account.` };
|
|
57223
|
+
if (/^\s*pending\s*:\s*This workflow is built and waiting for you to switch it on/i.test(check?.evidence ?? "")) {
|
|
57224
|
+
return {
|
|
57225
|
+
name: d.name,
|
|
57226
|
+
state: "DONE_UNCHECKED",
|
|
57227
|
+
reason: "Built as a draft and switched off until it is turned on.",
|
|
57228
|
+
evidence: check.evidence,
|
|
57229
|
+
plain: `${d.name} is in your account, switched off until it is turned on.`
|
|
57230
|
+
};
|
|
57231
|
+
}
|
|
57232
|
+
if (/^\s*invalid\s*:\s*Left as a draft on purpose/i.test(check?.evidence ?? "")) {
|
|
57233
|
+
const held2 = plainReason2(check?.evidence);
|
|
57234
|
+
return {
|
|
57235
|
+
name: d.name,
|
|
57236
|
+
state: "DONE_UNCHECKED",
|
|
57237
|
+
reason: held2,
|
|
57238
|
+
evidence: check.evidence,
|
|
57239
|
+
plain: `${d.name} is in your account and switched off. ${held2}`.trim()
|
|
57240
|
+
};
|
|
57241
|
+
}
|
|
56761
57242
|
const reason3 = plainReason2(check?.evidence, d.instruction) || `Check ${d.name}.`;
|
|
56762
57243
|
return {
|
|
56763
57244
|
name: d.name,
|
|
@@ -57403,15 +57884,15 @@ function fileSendModes(where) {
|
|
|
57403
57884
|
const file3 = () => typeof where === "string" ? where : where();
|
|
57404
57885
|
return {
|
|
57405
57886
|
get: (projectRef, locationId2) => lockedJsonUpdate(file3(), (data) => {
|
|
57406
|
-
const raw = data[
|
|
57887
|
+
const raw = data[keyOf4(projectRef, locationId2)];
|
|
57407
57888
|
if (raw === void 0) return { result: null };
|
|
57408
57889
|
const p = SendModeRecordSchema.safeParse(raw);
|
|
57409
57890
|
return { result: p.success ? p.data : null };
|
|
57410
57891
|
}),
|
|
57411
|
-
set: (rec2) => lockedJsonUpdate(file3(), (data) => ({ data: { ...data, [
|
|
57892
|
+
set: (rec2) => lockedJsonUpdate(file3(), (data) => ({ data: { ...data, [keyOf4(rec2.projectRef, rec2.locationId)]: rec2 }, result: "ok" })),
|
|
57412
57893
|
clear: (projectRef, locationId2) => lockedJsonUpdate(file3(), (data) => {
|
|
57413
57894
|
const rest = { ...data };
|
|
57414
|
-
delete rest[
|
|
57895
|
+
delete rest[keyOf4(projectRef, locationId2)];
|
|
57415
57896
|
return { data: rest, result: "ok" };
|
|
57416
57897
|
}),
|
|
57417
57898
|
clearAll: (projectRef) => lockedJsonUpdate(file3(), (data) => {
|
|
@@ -57467,7 +57948,7 @@ async function setSendMode(deps, req) {
|
|
|
57467
57948
|
});
|
|
57468
57949
|
return out;
|
|
57469
57950
|
}
|
|
57470
|
-
var import_zod81, SEND_MODE_MIN_DAYS, SEND_MODE_MIN_APPROVED_SHARE, SEND_MODE_MIN_REVIEWED, SendModeRecordSchema,
|
|
57951
|
+
var import_zod81, SEND_MODE_MIN_DAYS, SEND_MODE_MIN_APPROVED_SHARE, SEND_MODE_MIN_REVIEWED, SendModeRecordSchema, keyOf4, REVIEWED, SendModeRequest;
|
|
57471
57952
|
var init_send_mode = __esm({
|
|
57472
57953
|
"src/command-os/brain/send-mode.ts"() {
|
|
57473
57954
|
"use strict";
|
|
@@ -57494,7 +57975,7 @@ var init_send_mode = __esm({
|
|
|
57494
57975
|
/** The owner-presence signature over sendModePayloadBytes(this). */
|
|
57495
57976
|
signature: import_zod81.z.string()
|
|
57496
57977
|
}).strict();
|
|
57497
|
-
|
|
57978
|
+
keyOf4 = (projectRef, locationId2) => `${projectRef}/${locationId2}`;
|
|
57498
57979
|
REVIEWED = /* @__PURE__ */ new Set(["approved", "edited", "rejected"]);
|
|
57499
57980
|
SendModeRequest = import_zod81.z.object({
|
|
57500
57981
|
client: import_zod81.z.string().min(1).max(64),
|
|
@@ -59456,7 +59937,7 @@ var init_send = __esm({
|
|
|
59456
59937
|
|
|
59457
59938
|
// src/command-os/brain/approve-page.ts
|
|
59458
59939
|
function brainApprovePageHtml(data, seatToken = "", seatHeader = "x-command-os-seat") {
|
|
59459
|
-
const options = data.accounts.map((a) => `<option value="${
|
|
59940
|
+
const options = data.accounts.map((a) => `<option value="${esc13(a.locationId)}"${a.locationId === data.client?.locationId ? " selected" : ""}>${esc13(a.name)}</option>`).join("");
|
|
59460
59941
|
return `${cockpitHeadHtml("Brain drafts \xB7 Command OS", { extraCss: `
|
|
59461
59942
|
main.brainapp{display:grid;gap:var(--space-5);align-content:start;min-width:0}
|
|
59462
59943
|
.brainapp .items{display:grid;gap:var(--space-4);min-width:0}
|
|
@@ -59617,14 +60098,14 @@ async function loadLog(client){const ul=document.getElementById("sendlog");ul.te
|
|
|
59617
60098
|
clientSel.addEventListener("change",()=>{hideDone();load()});document.getElementById("refresh").addEventListener("click",load);load();
|
|
59618
60099
|
</script></body></html>`;
|
|
59619
60100
|
}
|
|
59620
|
-
var
|
|
60101
|
+
var esc13;
|
|
59621
60102
|
var init_approve_page = __esm({
|
|
59622
60103
|
"src/command-os/brain/approve-page.ts"() {
|
|
59623
60104
|
"use strict";
|
|
59624
60105
|
init_cockpit_shell();
|
|
59625
60106
|
init_script_json();
|
|
59626
60107
|
init_cockpit_approval();
|
|
59627
|
-
|
|
60108
|
+
esc13 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
59628
60109
|
}
|
|
59629
60110
|
});
|
|
59630
60111
|
|
|
@@ -60692,17 +61173,17 @@ function launcherScript(entry) {
|
|
|
60692
61173
|
return null;
|
|
60693
61174
|
}
|
|
60694
61175
|
function launcherOwnership(script) {
|
|
60695
|
-
if (!((0,
|
|
61176
|
+
if (!((0, import_node_path13.isAbsolute)(script) || import_node_path13.win32.isAbsolute(script))) return "foreign";
|
|
60696
61177
|
let real;
|
|
60697
61178
|
try {
|
|
60698
|
-
real = (0,
|
|
60699
|
-
if (!(0,
|
|
61179
|
+
real = (0, import_node_fs14.realpathSync)(script);
|
|
61180
|
+
if (!(0, import_node_fs14.statSync)(real).isFile()) return "foreign";
|
|
60700
61181
|
} catch {
|
|
60701
61182
|
return /ghl-command-mcp|ghl-mcp|@elitedcs/i.test(script) ? "unsure" : "foreign";
|
|
60702
61183
|
}
|
|
60703
|
-
if ((0,
|
|
61184
|
+
if ((0, import_node_path13.basename)(real) !== LAUNCHER) return "foreign";
|
|
60704
61185
|
try {
|
|
60705
|
-
const manifest = JSON.parse((0,
|
|
61186
|
+
const manifest = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)((0, import_node_path13.dirname)(real), "package.json"), "utf8"));
|
|
60706
61187
|
return object2(manifest) && manifest.name === "@elitedcs/ghl-mcp" ? "ours" : "foreign";
|
|
60707
61188
|
} catch {
|
|
60708
61189
|
return "foreign";
|
|
@@ -60715,7 +61196,7 @@ function ourLauncherRealPath(entry) {
|
|
|
60715
61196
|
const script = launcherScript(entry);
|
|
60716
61197
|
if (script === null || launcherOwnership(script) !== "ours") return null;
|
|
60717
61198
|
try {
|
|
60718
|
-
return (0,
|
|
61199
|
+
return (0, import_node_fs14.realpathSync)(script);
|
|
60719
61200
|
} catch {
|
|
60720
61201
|
return null;
|
|
60721
61202
|
}
|
|
@@ -60768,9 +61249,9 @@ function entryOwnership(entry) {
|
|
|
60768
61249
|
}
|
|
60769
61250
|
if (["node", "node.exe"].includes(command)) {
|
|
60770
61251
|
const script2 = entry.args[0];
|
|
60771
|
-
if (typeof script2 === "string" && ((0,
|
|
61252
|
+
if (typeof script2 === "string" && ((0, import_node_path13.isAbsolute)(script2) || import_node_path13.win32.isAbsolute(script2)) && /(?:^|\/)dist\/index\.js$/.test(script2.replace(/\\/g, "/"))) {
|
|
60772
61253
|
try {
|
|
60773
|
-
const manifest = JSON.parse((0,
|
|
61254
|
+
const manifest = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)((0, import_node_path13.dirname)((0, import_node_path13.dirname)(script2.replace(/\\/g, "/"))), "package.json"), "utf8"));
|
|
60774
61255
|
return manifest.name === "@elitedcs/ghl-mcp" ? "ours" : "foreign";
|
|
60775
61256
|
} catch {
|
|
60776
61257
|
return /ghl-command-mcp|ghl-mcp|@elitedcs/i.test(script2) ? "unsure" : "foreign";
|
|
@@ -60850,12 +61331,12 @@ function planDesktopEntry(existing, key2, desired, force = false, updateOurs = f
|
|
|
60850
61331
|
}
|
|
60851
61332
|
return { servers, action, oldKey, launcher, kept: describeLaunch(servers[key2]), removed: removed === void 0 ? void 0 : describeLaunch(removed), switchedFrom, switchedFromLocal, foreignLegacy: classifications.some(([name2, kind]) => name2 === "ghl" && kind === "foreign") };
|
|
60852
61333
|
}
|
|
60853
|
-
var
|
|
61334
|
+
var import_node_fs14, import_node_path13, import_node_util, object2, WIZARD_MANAGED_ENV, LAUNCHER, SHELLS, OUR_PACKAGE_ARG, SECRET_FLAG_WORDS, isSecretFlag, NAME_VALUE, KNOWN_KEY, HIDDEN;
|
|
60854
61335
|
var init_desktop_entry = __esm({
|
|
60855
61336
|
"src/desktop-entry.mjs"() {
|
|
60856
61337
|
"use strict";
|
|
60857
|
-
|
|
60858
|
-
|
|
61338
|
+
import_node_fs14 = require("node:fs");
|
|
61339
|
+
import_node_path13 = require("node:path");
|
|
60859
61340
|
import_node_util = require("node:util");
|
|
60860
61341
|
object2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
60861
61342
|
WIZARD_MANAGED_ENV = Object.freeze([
|
|
@@ -61144,19 +61625,19 @@ async function switchClaudeCodeChannel(io, opts, from, to, launch) {
|
|
|
61144
61625
|
}
|
|
61145
61626
|
function defaultHasSavedSetup() {
|
|
61146
61627
|
try {
|
|
61147
|
-
return (0,
|
|
61628
|
+
return (0, import_node_fs15.existsSync)(credentialsPath());
|
|
61148
61629
|
} catch {
|
|
61149
61630
|
return false;
|
|
61150
61631
|
}
|
|
61151
61632
|
}
|
|
61152
|
-
var import_node_child_process5,
|
|
61633
|
+
var import_node_child_process5, import_node_fs15, GHL_SERVER_NAME, CLAUDE_CODE_ADD_ARGS, CLAUDE_CODE_INSTALL_LINE, CLAUDE_CODE_ADD_HELP, OURS, defaultRunner;
|
|
61153
61634
|
var init_claude_code_registration = __esm({
|
|
61154
61635
|
"src/claude-code-registration.ts"() {
|
|
61155
61636
|
"use strict";
|
|
61156
61637
|
import_node_child_process5 = require("node:child_process");
|
|
61157
61638
|
init_claude_locate();
|
|
61158
61639
|
init_running_channel();
|
|
61159
|
-
|
|
61640
|
+
import_node_fs15 = require("node:fs");
|
|
61160
61641
|
init_credentials_store();
|
|
61161
61642
|
init_desktop_entry();
|
|
61162
61643
|
GHL_SERVER_NAME = "ghl";
|
|
@@ -61400,8 +61881,8 @@ function validateRunRequest(input) {
|
|
|
61400
61881
|
return { ok: true, value };
|
|
61401
61882
|
}
|
|
61402
61883
|
function designRunsDir() {
|
|
61403
|
-
const dir = (0,
|
|
61404
|
-
(0,
|
|
61884
|
+
const dir = (0, import_node_path14.join)(appDataDir(), "design-runs");
|
|
61885
|
+
(0, import_node_fs16.mkdirSync)(dir, { recursive: true });
|
|
61405
61886
|
return dir;
|
|
61406
61887
|
}
|
|
61407
61888
|
function argvFor(r, outDir) {
|
|
@@ -61422,8 +61903,8 @@ function startDesignRun(request2, opts = {}) {
|
|
|
61422
61903
|
if (!checked.ok) throw new Error(checked.error);
|
|
61423
61904
|
request2 = checked.value;
|
|
61424
61905
|
const id2 = (0, import_node_crypto29.randomUUID)();
|
|
61425
|
-
const outDir = (0,
|
|
61426
|
-
(0,
|
|
61906
|
+
const outDir = (0, import_node_path14.join)(designRunsDir(), id2);
|
|
61907
|
+
(0, import_node_fs16.mkdirSync)(outDir, { recursive: true });
|
|
61427
61908
|
const job = { id: id2, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), outDir, request: request2, tail: [] };
|
|
61428
61909
|
jobs.set(id2, job);
|
|
61429
61910
|
const entry = opts.entry ?? process.argv[1];
|
|
@@ -61434,11 +61915,11 @@ function startDesignRun(request2, opts = {}) {
|
|
|
61434
61915
|
env: { ...process.env, CLAUDECODE: "", CLAUDE_CODE_ENTRYPOINT: "" }
|
|
61435
61916
|
});
|
|
61436
61917
|
children.set(id2, child);
|
|
61437
|
-
const log = (0,
|
|
61918
|
+
const log = (0, import_node_path14.join)(outDir, "run.log");
|
|
61438
61919
|
const take = (chunk) => {
|
|
61439
61920
|
const text3 = String(chunk);
|
|
61440
61921
|
try {
|
|
61441
|
-
(0,
|
|
61922
|
+
(0, import_node_fs16.appendFileSync)(log, text3);
|
|
61442
61923
|
} catch {
|
|
61443
61924
|
}
|
|
61444
61925
|
for (const line4 of text3.split("\n").map((l) => l.trimEnd()).filter(Boolean)) {
|
|
@@ -61469,10 +61950,10 @@ function designRunStatus(id2) {
|
|
|
61469
61950
|
const job = jobs.get(id2);
|
|
61470
61951
|
if (!job) return null;
|
|
61471
61952
|
const shown = { ...job, tail: memberRunLines(job.tail) };
|
|
61472
|
-
const runJson = (0,
|
|
61473
|
-
if (!(0,
|
|
61953
|
+
const runJson = (0, import_node_path14.join)(job.outDir, "run.json");
|
|
61954
|
+
if (!(0, import_node_fs16.existsSync)(runJson)) return shown;
|
|
61474
61955
|
try {
|
|
61475
|
-
return { ...shown, run: JSON.parse((0,
|
|
61956
|
+
return { ...shown, run: JSON.parse((0, import_node_fs16.readFileSync)(runJson, "utf8")) };
|
|
61476
61957
|
} catch {
|
|
61477
61958
|
return shown;
|
|
61478
61959
|
}
|
|
@@ -61481,11 +61962,11 @@ function designResultFile(id2) {
|
|
|
61481
61962
|
const job = jobs.get(id2);
|
|
61482
61963
|
if (!job || job.status !== "done") return null;
|
|
61483
61964
|
try {
|
|
61484
|
-
const run = JSON.parse((0,
|
|
61965
|
+
const run = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path14.join)(job.outDir, "run.json"), "utf8"));
|
|
61485
61966
|
if (typeof run.file !== "string" || !/\.html?$/i.test(run.file)) return null;
|
|
61486
|
-
const root = (0,
|
|
61487
|
-
const file3 = (0,
|
|
61488
|
-
return file3.startsWith(root +
|
|
61967
|
+
const root = (0, import_node_fs16.realpathSync)(job.outDir);
|
|
61968
|
+
const file3 = (0, import_node_fs16.realpathSync)((0, import_node_path14.isAbsolute)(run.file) ? run.file : (0, import_node_path14.join)(job.outDir, run.file));
|
|
61969
|
+
return file3.startsWith(root + import_node_path14.sep) ? file3 : null;
|
|
61489
61970
|
} catch {
|
|
61490
61971
|
return null;
|
|
61491
61972
|
}
|
|
@@ -61505,13 +61986,13 @@ function cancelDesignRun(id2) {
|
|
|
61505
61986
|
}
|
|
61506
61987
|
return true;
|
|
61507
61988
|
}
|
|
61508
|
-
var import_node_child_process6,
|
|
61989
|
+
var import_node_child_process6, import_node_fs16, import_node_path14, import_node_crypto29, SLUG2, MODEL, JOINED_FLAG, jobs, children, CHATTER, PROBLEM2, ABS_PATH;
|
|
61509
61990
|
var init_run_jobs = __esm({
|
|
61510
61991
|
"src/command-os/design/run-jobs.ts"() {
|
|
61511
61992
|
"use strict";
|
|
61512
61993
|
import_node_child_process6 = require("node:child_process");
|
|
61513
|
-
|
|
61514
|
-
|
|
61994
|
+
import_node_fs16 = require("node:fs");
|
|
61995
|
+
import_node_path14 = require("node:path");
|
|
61515
61996
|
import_node_crypto29 = require("node:crypto");
|
|
61516
61997
|
init_credentials_store();
|
|
61517
61998
|
init_schema();
|
|
@@ -65220,199 +65701,6 @@ var init_client_intake = __esm({
|
|
|
65220
65701
|
}
|
|
65221
65702
|
});
|
|
65222
65703
|
|
|
65223
|
-
// src/build-facts.ts
|
|
65224
|
-
function customValueKeyOf(name2) {
|
|
65225
|
-
return name2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
65226
|
-
}
|
|
65227
|
-
function customValueStatusOf(planValues, accountValues, now = () => /* @__PURE__ */ new Date()) {
|
|
65228
|
-
const readAt = now().toISOString();
|
|
65229
|
-
if (!accountValues || planValues.length > 0 && accountValues.length === 0) return { filled: [], empty: [], unreadable: true, readAt };
|
|
65230
|
-
const byKey = new Map(accountValues.map((v) => [customValueKeyOf(v.name), v]));
|
|
65231
|
-
const filled2 = [];
|
|
65232
|
-
const empty = [];
|
|
65233
|
-
const unknown = [];
|
|
65234
|
-
for (const cv of planValues) {
|
|
65235
|
-
const live = byKey.get(customValueKeyOf(cv.name));
|
|
65236
|
-
if (!live) empty.push(cv.name);
|
|
65237
|
-
else if (typeof live.value !== "string") unknown.push(cv.name);
|
|
65238
|
-
else if (live.value.trim()) filled2.push(cv.name);
|
|
65239
|
-
else empty.push(cv.name);
|
|
65240
|
-
}
|
|
65241
|
-
return { filled: filled2, empty, ...unknown.length ? { unknown } : {}, readAt };
|
|
65242
|
-
}
|
|
65243
|
-
function handoffCustomValueNote(handoffRef, planValues, status) {
|
|
65244
|
-
if (!status || status.unreadable) return "";
|
|
65245
|
-
const covered = planValues.filter((cv) => cv.filledBy === handoffRef).map((cv) => cv.name);
|
|
65246
|
-
if (!covered.length) return "";
|
|
65247
|
-
const filled2 = covered.filter((n) => status.filled.includes(n));
|
|
65248
|
-
const empty = covered.filter((n) => status.empty.includes(n));
|
|
65249
|
-
const list4 = (xs) => xs.length <= 1 ? xs.join("") : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
65250
|
-
const parts = [
|
|
65251
|
-
filled2.length ? `${list4(filled2)} ${filled2.length === 1 ? "is" : "are"} filled` : "",
|
|
65252
|
-
empty.length ? `${list4(empty)} ${empty.length === 1 ? "is" : "are"} empty` : ""
|
|
65253
|
-
].filter(Boolean);
|
|
65254
|
-
return parts.length ? ` Read back from the account after this build: ${parts.join("; ")}.` : "";
|
|
65255
|
-
}
|
|
65256
|
-
function pieces(text3) {
|
|
65257
|
-
const guarded2 = text3.replace(/\bDr\./g, "Dr\0");
|
|
65258
|
-
const out = [];
|
|
65259
|
-
const re = /([\s\S]*?(?:[.!?](?=\s|$)|(?=\n)|$))(\s*)/g;
|
|
65260
|
-
let m;
|
|
65261
|
-
while ((m = re.exec(guarded2)) && m[0].length) out.push({ sentence: m[1].replace(/Dr\u0000/g, "Dr."), sep: m[2] });
|
|
65262
|
-
return out;
|
|
65263
|
-
}
|
|
65264
|
-
function rejoin(parts) {
|
|
65265
|
-
const kept = parts.filter((p) => p !== null && p.sentence.trim() !== "");
|
|
65266
|
-
return kept.map((p, i) => p.sentence + (i < kept.length - 1 ? p.sep : "")).join("");
|
|
65267
|
-
}
|
|
65268
|
-
function claimsVerifiedPersonNotReal(sentence, verifiedStaff) {
|
|
65269
|
-
for (const raw of verifiedStaff) {
|
|
65270
|
-
const name2 = raw.trim();
|
|
65271
|
-
if (!name2) continue;
|
|
65272
|
-
const n = esc13(name2);
|
|
65273
|
-
const after = new RegExp(String.raw`\b${n}\b(?:,\s*|\s+(?:is|was|looks like|appears to be|seems to be|may be|might be)\s+|\s+(?:isn'?t|wasn'?t)\s+)(?:only\s+|just\s+|still\s+|probably\s+)?(?:(?:a|an|the)\s+)?${NOT_REAL_PHRASE}`, "i");
|
|
65274
|
-
const before = new RegExp(String.raw`\b(?:placeholder|fake|dummy|test|sample|demo)(?: (?:user|staff member|person|provider|name))?,?\s+(?:named\s+|called\s+)?${n}\b`, "i");
|
|
65275
|
-
const isnt = new RegExp(String.raw`\b${n}\b\s+(?:isn'?t|is not|wasn'?t|was not)\s+(?:(?:a|an)\s+)?(?:real|actual|genuine)\b`, "i");
|
|
65276
|
-
if (after.test(sentence) || isnt.test(sentence) || before.test(sentence)) return true;
|
|
65277
|
-
}
|
|
65278
|
-
return false;
|
|
65279
|
-
}
|
|
65280
|
-
function mentionPattern(key2) {
|
|
65281
|
-
const words2 = customValueKeyOf(key2).split("_").map(esc13);
|
|
65282
|
-
return new RegExp(`\\b${words2.join("[\\s_]+")}\\b`, "gi");
|
|
65283
|
-
}
|
|
65284
|
-
function correctClause(clause, status) {
|
|
65285
|
-
const pred = new RegExp(EMPTY_PRED, "i").exec(clause);
|
|
65286
|
-
if (!pred) return { text: clause, changed: false };
|
|
65287
|
-
const head4 = clause.slice(0, pred.index);
|
|
65288
|
-
const keys = [...status.filled, ...status.empty, ...status.unknown ?? []];
|
|
65289
|
-
const found = [];
|
|
65290
|
-
for (const key2 of keys) for (const m of head4.matchAll(mentionPattern(key2))) found.push({ key: key2, start: m.index, end: m.index + m[0].length });
|
|
65291
|
-
if (!found.length) return { text: clause, changed: false };
|
|
65292
|
-
found.sort((a, b) => a.start - b.start);
|
|
65293
|
-
const last = found[found.length - 1];
|
|
65294
|
-
const tail = head4.slice(last.end);
|
|
65295
|
-
if (!/^\s*(?:custom values?\s*)?(?:\([^)]*\)\s*)?$/i.test(tail)) return { text: clause, changed: false };
|
|
65296
|
-
for (let i = 1; i < found.length; i++) if (!/^\s*(?:,|and|,\s*and|&)\s*$/i.test(head4.slice(found[i - 1].end, found[i].start))) return { text: clause, changed: false };
|
|
65297
|
-
const filledNamed = found.filter((f) => status.filled.includes(f.key));
|
|
65298
|
-
if (!filledNamed.length) return { text: clause, changed: false };
|
|
65299
|
-
const stillEmpty = found.filter((f) => status.empty.includes(f.key));
|
|
65300
|
-
if (!stillEmpty.length) return { text: "", changed: true };
|
|
65301
|
-
const listed = listOf(stillEmpty.map((f) => head4.slice(f.start, f.end)));
|
|
65302
|
-
let out = clause.slice(0, found[0].start) + listed + clause.slice(last.end);
|
|
65303
|
-
if (stillEmpty.length === 1 && found.length > 1) {
|
|
65304
|
-
return { text: out.replace(/\bcustom values\b/i, "custom value").replace(/\b(are|were)(?=\s+(?:still\s+)?(?:left\s+)?(?:empty|blank|unfilled|not|missing)\b)/i, (v) => v.toLowerCase() === "are" ? "is" : "was"), changed: true };
|
|
65305
|
-
}
|
|
65306
|
-
return { text: out, changed: true };
|
|
65307
|
-
}
|
|
65308
|
-
function maskAndNames(clause, keys) {
|
|
65309
|
-
const masked = [];
|
|
65310
|
-
let text3 = clause;
|
|
65311
|
-
for (const key2 of keys.filter((k) => /(?:^|_)and(?:_|$)/.test(customValueKeyOf(k)))) {
|
|
65312
|
-
text3 = text3.replace(mentionPattern(key2), (m) => {
|
|
65313
|
-
masked.push(m);
|
|
65314
|
-
return `${masked.length - 1}`;
|
|
65315
|
-
});
|
|
65316
|
-
}
|
|
65317
|
-
return { text: text3, unmask: (t) => t.replace(/\u0001(\d+)\u0001/g, (_m, i) => masked[Number(i)]) };
|
|
65318
|
-
}
|
|
65319
|
-
function splitAndClaims(clause, keys = []) {
|
|
65320
|
-
const { text: text3, unmask } = maskAndNames(clause, keys);
|
|
65321
|
-
return splitMasked(text3).map(unmask);
|
|
65322
|
-
}
|
|
65323
|
-
function claimsWithin(clause, keys) {
|
|
65324
|
-
const { text: text3, unmask } = maskAndNames(clause, keys);
|
|
65325
|
-
const claims = (t) => new RegExp(EMPTY_PRED, "i").test(t);
|
|
65326
|
-
const bits = text3.split(/(,?\s+and\s+)/i);
|
|
65327
|
-
let from = claims(bits[0]) ? 0 : -1;
|
|
65328
|
-
for (let i = 2; from < 0 && i < bits.length; i += 2) if (claims(bits.slice(i).join(""))) from = i;
|
|
65329
|
-
if (from < 0) return [];
|
|
65330
|
-
return splitMasked(bits.slice(from).join("")).map(unmask).filter((_, i) => i % 2 === 0).filter(claims);
|
|
65331
|
-
}
|
|
65332
|
-
function splitMasked(clause) {
|
|
65333
|
-
const bits = clause.split(/(,?\s+and\s+)/i);
|
|
65334
|
-
const out = [bits[0]];
|
|
65335
|
-
const claims = (t) => new RegExp(EMPTY_PRED, "i").test(t);
|
|
65336
|
-
for (let i = 1; i < bits.length; i += 2) {
|
|
65337
|
-
const joiner = bits[i], next = bits[i + 1] ?? "";
|
|
65338
|
-
if (claims(out[out.length - 1]) && claims(next)) out.push(joiner, next);
|
|
65339
|
-
else out[out.length - 1] += joiner + next;
|
|
65340
|
-
}
|
|
65341
|
-
return out;
|
|
65342
|
-
}
|
|
65343
|
-
function correctEmptinessClaim(sentence, status) {
|
|
65344
|
-
if (!status || status.unreadable) return { text: sentence, changed: false };
|
|
65345
|
-
const keys = [...status.filled, ...status.empty, ...status.unknown ?? []];
|
|
65346
|
-
const parts = [];
|
|
65347
|
-
const outer = sentence.split(CLAUSE_SPLIT);
|
|
65348
|
-
for (let i = 0; i < outer.length; i += 2) {
|
|
65349
|
-
parts.push(...splitAndClaims(outer[i], keys));
|
|
65350
|
-
if (i + 1 < outer.length) parts.push(outer[i + 1]);
|
|
65351
|
-
}
|
|
65352
|
-
let changed = false;
|
|
65353
|
-
const kept = [];
|
|
65354
|
-
let dropNextConsequence = false;
|
|
65355
|
-
for (let i = 0; i < parts.length; i += 2) {
|
|
65356
|
-
const joiner = parts[i - 1] ?? "";
|
|
65357
|
-
if (dropNextConsequence && /^\s*,?\s*(?:and so|so)\s+$/i.test(joiner)) {
|
|
65358
|
-
changed = true;
|
|
65359
|
-
for (const piece of claimsWithin(parts[i], keys)) {
|
|
65360
|
-
const c2 = correctClause(piece, status);
|
|
65361
|
-
if (c2.text.trim()) kept.push({ text: c2.text, joiner: ", and " });
|
|
65362
|
-
}
|
|
65363
|
-
continue;
|
|
65364
|
-
}
|
|
65365
|
-
dropNextConsequence = false;
|
|
65366
|
-
const c = correctClause(parts[i], status);
|
|
65367
|
-
if (c.changed) changed = true;
|
|
65368
|
-
if (c.text.trim()) kept.push({ text: c.text, joiner });
|
|
65369
|
-
else dropNextConsequence = true;
|
|
65370
|
-
}
|
|
65371
|
-
if (!changed) return { text: sentence, changed: false };
|
|
65372
|
-
let text3 = kept.map((k, i) => (i ? k.joiner : "") + k.text).join("").trim();
|
|
65373
|
-
if (text3 && !/[.!?]$/.test(text3)) text3 += ".";
|
|
65374
|
-
text3 = text3.charAt(0).toUpperCase() + text3.slice(1);
|
|
65375
|
-
return { text: /^[.!?]?$/.test(text3) ? "" : text3, changed: true };
|
|
65376
|
-
}
|
|
65377
|
-
function guardBuildReport(report3, facts) {
|
|
65378
|
-
const staff = (facts.verifiedStaff ?? []).filter((n) => n.trim());
|
|
65379
|
-
const others = (facts.unverifiedStaff ?? []).filter((n) => n.trim());
|
|
65380
|
-
const fix = (text3) => {
|
|
65381
|
-
let staffClaim = false, changed = false;
|
|
65382
|
-
const parts = pieces(text3).map((p) => {
|
|
65383
|
-
if (staff.length && claimsVerifiedPersonNotReal(p.sentence, staff)) {
|
|
65384
|
-
staffClaim = true;
|
|
65385
|
-
changed = true;
|
|
65386
|
-
return null;
|
|
65387
|
-
}
|
|
65388
|
-
const bullet3 = /^(\s*(?:[-*•]|\d+[.)])\s+)/.exec(p.sentence)?.[1] ?? "";
|
|
65389
|
-
const c = correctEmptinessClaim(p.sentence.slice(bullet3.length), facts.customValues);
|
|
65390
|
-
if (!c.changed) return p;
|
|
65391
|
-
changed = true;
|
|
65392
|
-
return c.text ? { sentence: bullet3 + c.text, sep: p.sep } : null;
|
|
65393
|
-
});
|
|
65394
|
-
return { text: changed ? rejoin(parts) : text3, staffClaim };
|
|
65395
|
-
};
|
|
65396
|
-
const namesOther = (text3) => others.some((n) => new RegExp(`\\b${esc13(n)}\\b`, "i").test(text3) || new RegExp(`\\b${esc13(n.split(/\s+/)[0])}\\b`).test(text3));
|
|
65397
|
-
const out = { ...report3 };
|
|
65398
|
-
if (typeof report3.summary === "string") out.summary = fix(report3.summary).text;
|
|
65399
|
-
if (Array.isArray(report3.issues)) {
|
|
65400
|
-
out.issues = report3.issues.map((x) => ({ original: x, ...fix(x) })).filter((r) => r.text.trim() && !(r.staffClaim && !namesOther(r.original))).map((r) => r.text);
|
|
65401
|
-
}
|
|
65402
|
-
return out;
|
|
65403
|
-
}
|
|
65404
|
-
var esc13, listOf, NOT_REAL_PHRASE, EMPTY_PRED, CLAUSE_SPLIT;
|
|
65405
|
-
var init_build_facts = __esm({
|
|
65406
|
-
"src/build-facts.ts"() {
|
|
65407
|
-
"use strict";
|
|
65408
|
-
esc13 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
65409
|
-
listOf = (xs) => xs.length <= 1 ? xs.join("") : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
65410
|
-
NOT_REAL_PHRASE = String.raw`(?:placeholders?|not (?:a |an )?(?:real|actual|genuine)\b|fake|dummy|made[- ]up|stand-?ins?|(?:test|sample|example|demo) (?:users?|accounts?|staff(?: members?)?|persons?|people|providers?|members?|names?|entr(?:y|ies)))`;
|
|
65411
|
-
EMPTY_PRED = String.raw`\b(?:is|are|was|were|remains?|stays?)\s+(?:still\s+)?(?:left\s+)?(?:empty|blank|unfilled|not (?:yet )?(?:been )?(?:filled(?: in)?|set|entered)|missing)\b(?!\s+(?:its|their|the|a|an|from|in the)\b)`;
|
|
65412
|
-
CLAUSE_SPLIT = /(;\s*|,\s*(?:but|while|although|though|so|and so|because|whereas)\s+|\s+but\s+)/i;
|
|
65413
|
-
}
|
|
65414
|
-
});
|
|
65415
|
-
|
|
65416
65704
|
// src/build-steps.ts
|
|
65417
65705
|
function stepProgress(i) {
|
|
65418
65706
|
const stage = (n) => i.stages[n] === "done" && i.stale.indexOf(n) < 0 && i.running !== n;
|
|
@@ -65652,13 +65940,14 @@ function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
|
65652
65940
|
/* OS audit F11 (command-center ruling (c), 2026-10-30): what the client bought is asked before the Brief, so the
|
|
65653
65941
|
Blueprint Review is written for exactly what was sold and the normal order never leaves it out of date. */
|
|
65654
65942
|
if(n===5){bsHideAssess=true;try{box.appendChild(scopeBlock(id,state))}finally{bsHideAssess=false}return}
|
|
65655
|
-
if(n===6){bsOutcome(c,state,1,box);bsRun(c,state,run,1,(((state.clients[id]||{}).stages||[])[1]==="done")?"Write the Brief again":"Write the Brief",box);return}
|
|
65943
|
+
if(n===6){/* OS re-check LOW: a re-run never sits under the last run's message */if(!(run&&run.running&&run.locationId===id&&run.stage===1))bsOutcome(c,state,1,box);bsRun(c,state,run,1,(((state.clients[id]||{}).stages||[])[1]==="done")?"Write the Brief again":"Write the Brief",box);return}
|
|
65656
65944
|
if(n===7){bsStaffStep(c,state,box);return}
|
|
65657
65945
|
if(n===8){const a=bsInputs(c,state,null).assessment;
|
|
65658
65946
|
if(a!=="attached"&&a!=="without"){box.appendChild(mk("p","cds-callout warn","Approve waits for step 4: attach the Assessment there, or choose Build without an Assessment."));box.appendChild(bsBtn("Open step 4",()=>{bsChange[id+":3"]=bsSummary(3,c,state);render(lastState,lastRun)},true))}
|
|
65659
65947
|
if(!rData[id])rLoad(id);bsHideAssess=true;try{box.appendChild(unnest(reviewPanel(id,c.name,state,run)))}finally{bsHideAssess=false}return}
|
|
65660
65948
|
if(n===9){const bp=((state.buildPreviews||{})[id]);const live=run&&run.running&&run.locationId===id&&run.stage===3;
|
|
65661
|
-
|
|
65949
|
+
/* rc.7 live finding: a Build that failed after the list was made (signed out) says why above the list. */
|
|
65950
|
+
if(bp&&!bp.confirmed&&!live){const oc3=(state.outcomes||{})[id+":3"];if(oc3&&oc3.ok===false&&oc3.at&&bp.preview.at&&oc3.at>bp.preview.at)bsOutcome(c,state,3,box);bsPause(c,state,bp.preview,box);return}
|
|
65662
65951
|
bsOutcome(c,state,3,box);
|
|
65663
65952
|
/* Run 8 finding 4: it ended on just "Built." The finish says what was made, counted from the build's own record. */
|
|
65664
65953
|
if(!live&&(((state.clients||{})[id]||{}).stages||[])[3]==="done"){const bv0=(((state.buildViews||{})[id])||{});const made=(bv0.made&&bv0.made.length)?bv0.made:(bv0.built||[]).filter(x=>x.status==="created");
|
|
@@ -65693,12 +65982,15 @@ function bsPause(c,state,pv,box){const id=c.locationId;const key="confirm:"+id;
|
|
|
65693
65982
|
top of. No building on top: undo that build first (Claude lists what it removes and waits for DELETE), or stop. */
|
|
65694
65983
|
if((pv.earlierBuild||[]).length){const eb=pv.earlierBuild,cn=c.name||"This account",runs=[...new Set(eb.map(e=>e.runId))];
|
|
65695
65984
|
const w=mk("div","cds-callout warn bs-earlier");w.setAttribute("role","alert");
|
|
65985
|
+
/* OS re-check R1: the earlier build was undone after this list was made, so the list is out of date: offer a new one. */
|
|
65986
|
+
const bp0=(state.buildPreviews||{})[id];if(bp0&&bp0.earlierUndone){w.appendChild(mk("p","","The earlier build has been undone since this list was made, so the list is out of date. Start the build to make a new one; nothing is written until you confirm it."));box.appendChild(w);bsRun(c,state,lastRun||{},3,"Start the build",box);return}
|
|
65696
65987
|
w.appendChild(mk("p","",cn+" already has "+(eb.length===1?"an item":eb.length+" items")+" an earlier build of a different plan made: "+eb.map(e=>"\u201c"+e.name+"\u201d").join(", ")+". Building now would build on top of "+(eb.length===1?"it":"them")+", which is how duplicates happen, so Build will not start. Undo the earlier build first, or stop here."));
|
|
65697
65988
|
w.appendChild(mk("b","","Undo the earlier build: ask Claude"));
|
|
65698
65989
|
for(const r of runs){const q=mk("p","say","Undo build run "+r+" for "+cn);w.appendChild(q)}
|
|
65699
65990
|
w.appendChild(mk("p","muted","Claude lists everything it would remove first, and removes nothing until you type DELETE. Then come back and Start the build."));
|
|
65700
65991
|
w.appendChild(mk("b","","Or stop here"));w.appendChild(mk("p","muted","This build has not written anything to "+cn+". Leave this page; nothing changes."));
|
|
65701
|
-
|
|
65992
|
+
/* OS re-check R1: after the undo, the member is told to come back and Start the build, so the button is here. */
|
|
65993
|
+
box.appendChild(w);bsRun(c,state,lastRun||{},3,"Start the build",box,true);return}
|
|
65702
65994
|
box.appendChild(mk("p","","Nothing has been written to "+(c.name||"this account")+" yet. Here is what Build will do. Check it, then confirm."));
|
|
65703
65995
|
if((pv.unrecordedDrafts||[]).length)box.appendChild(mk("p","cds-callout info","Already in the account as drafts, made by no build on record: "+pv.unrecordedDrafts.map(n=>"\u201c"+n+"\u201d").join(", ")+". Build uses them as they are; check they are the ones you mean."));
|
|
65704
65996
|
/* Walk 2026-10-28 W13/W8: "17 emails, 8 email templates" said nothing about the difference, and the list named nothing.
|
|
@@ -71522,11 +71814,11 @@ function registerAssessmentTools(server2, client) {
|
|
|
71522
71814
|
warnings.push(`"${svc.name}" covers ${covers.length ? covers.map((c) => `"${c}"`).join(", ") : "nothing"}, and this assessment found ${covers.length ? "none of those" : "no finding for it"}. It is priced in the investment total but the report never says what it is for.`);
|
|
71523
71815
|
}
|
|
71524
71816
|
}
|
|
71525
|
-
const dir = args.outDir ?? (0,
|
|
71526
|
-
(0,
|
|
71817
|
+
const dir = args.outDir ?? (0, import_node_path15.join)((0, import_node_os.homedir)(), "Documents", "assessments");
|
|
71818
|
+
(0, import_node_fs17.mkdirSync)(dir, { recursive: true });
|
|
71527
71819
|
const slug3 = (state2.biz || "assessment").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
71528
|
-
const file3 = (0,
|
|
71529
|
-
(0,
|
|
71820
|
+
const file3 = (0, import_node_path15.join)(dir, `${slug3}-${(0, import_node_crypto31.randomBytes)(4).toString("hex")}.html`);
|
|
71821
|
+
(0, import_node_fs17.writeFileSync)(file3, html2, "utf8");
|
|
71530
71822
|
return {
|
|
71531
71823
|
written: file3,
|
|
71532
71824
|
verdict: r.verdict,
|
|
@@ -71548,7 +71840,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
71548
71840
|
}
|
|
71549
71841
|
);
|
|
71550
71842
|
}
|
|
71551
|
-
var import_node_util4, import_zod92, import_node_crypto31,
|
|
71843
|
+
var import_node_util4, import_zod92, import_node_crypto31, import_node_fs17, import_node_os, import_node_path15, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema, PIPELINE_READBACK_WAITS_MS;
|
|
71552
71844
|
var init_assessment = __esm({
|
|
71553
71845
|
"src/tools/assessment.ts"() {
|
|
71554
71846
|
"use strict";
|
|
@@ -71562,9 +71854,9 @@ var init_assessment = __esm({
|
|
|
71562
71854
|
init_engine();
|
|
71563
71855
|
init_report();
|
|
71564
71856
|
import_node_crypto31 = require("node:crypto");
|
|
71565
|
-
|
|
71857
|
+
import_node_fs17 = require("node:fs");
|
|
71566
71858
|
import_node_os = require("node:os");
|
|
71567
|
-
|
|
71859
|
+
import_node_path15 = require("node:path");
|
|
71568
71860
|
QUESTION_UNITS = new Map(Q.map((q3) => [q3[0], q3[4]]));
|
|
71569
71861
|
AREA_TITLE = new Map(AREAS.map((a) => [a[0], a[1]]));
|
|
71570
71862
|
ASK = new Map(Q.map((q3) => [q3[0], q3[2]]));
|
|
@@ -71715,205 +72007,6 @@ var init_shared_brain = __esm({
|
|
|
71715
72007
|
}
|
|
71716
72008
|
});
|
|
71717
72009
|
|
|
71718
|
-
// src/build-confirm.ts
|
|
71719
|
-
function previewFingerprint(p) {
|
|
71720
|
-
return contentHash2({
|
|
71721
|
-
locationId: p.locationId,
|
|
71722
|
-
planHash: p.planHash,
|
|
71723
|
-
reuse: p.reuse.map((e) => e.key).sort(),
|
|
71724
|
-
change: p.change.map((e) => [e.key, (e.changes ?? []).map(stableChange).sort(), e.state ?? ""]).sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
|
|
71725
|
-
// 3.90.1 (Codex on e4734bc4): the logins the confirmation names are part of what was confirmed.
|
|
71726
|
-
logins: (p.logins ?? []).map((l) => `${l.email.trim().toLowerCase()}|${l.name}|${l.role}`).sort()
|
|
71727
|
-
});
|
|
71728
|
-
}
|
|
71729
|
-
function previewNamesLogins(preview) {
|
|
71730
|
-
return Boolean(preview && Array.isArray(preview.logins));
|
|
71731
|
-
}
|
|
71732
|
-
function composePreview(input) {
|
|
71733
|
-
const reuse = [];
|
|
71734
|
-
const change = [];
|
|
71735
|
-
const createByType = {};
|
|
71736
|
-
const createNames = {};
|
|
71737
|
-
let createCount = 0;
|
|
71738
|
-
const seen = /* @__PURE__ */ new Set();
|
|
71739
|
-
for (const item of input.items) {
|
|
71740
|
-
if (item.status !== "existing" || !item.existingId) {
|
|
71741
|
-
if (item.status === "would_create") {
|
|
71742
|
-
createCount++;
|
|
71743
|
-
createByType[item.type] = (createByType[item.type] ?? 0) + 1;
|
|
71744
|
-
const filled2 = input.bookingCalendar && item.type === "customValues" && customValueKeyOf(item.name) === "booking_link";
|
|
71745
|
-
if (item.name) (createNames[item.type] ??= []).push(filled2 ? `${item.name}, filled with the ${input.bookingCalendar} booking page` : item.name);
|
|
71746
|
-
}
|
|
71747
|
-
continue;
|
|
71748
|
-
}
|
|
71749
|
-
if (input.ours.has(item.existingId)) continue;
|
|
71750
|
-
const key2 = keyOf4(item.type, item.existingId);
|
|
71751
|
-
if (seen.has(key2)) continue;
|
|
71752
|
-
seen.add(key2);
|
|
71753
|
-
const changes = item.type === "workflows" ? input.workflowChanges.get(item.existingId) ?? [] : [];
|
|
71754
|
-
const state2 = input.workflowStates?.get(item.existingId);
|
|
71755
|
-
if (changes.length) change.push({ key: key2, type: item.type, name: item.name, id: item.existingId, changes: [...changes], ...state2 ? { state: state2 } : {} });
|
|
71756
|
-
else reuse.push({ key: key2, type: item.type, name: item.name, id: item.existingId });
|
|
71757
|
-
}
|
|
71758
|
-
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] });
|
|
71759
|
-
const earlierBuild = [];
|
|
71760
|
-
const earlierSeen = /* @__PURE__ */ new Set();
|
|
71761
|
-
for (const item of input.items) {
|
|
71762
|
-
const hit = item.status === "existing" && item.existingId ? input.otherPlans?.get(item.existingId) : void 0;
|
|
71763
|
-
if (hit && !earlierSeen.has(item.existingId)) {
|
|
71764
|
-
earlierSeen.add(item.existingId);
|
|
71765
|
-
earlierBuild.push({ type: item.type, name: item.name || hit.name, runId: hit.runId });
|
|
71766
|
-
}
|
|
71767
|
-
}
|
|
71768
|
-
const unrecordedDrafts = [...reuse, ...change].filter((e) => e.type === "workflows" && input.workflowStatus?.get(e.id) === "draft").map((e) => e.name);
|
|
71769
|
-
const core = { locationId: input.locationId, planHash: input.planHash, reuse, change, logins: [...input.logins ?? []] };
|
|
71770
|
-
return {
|
|
71771
|
-
v: 1,
|
|
71772
|
-
recordKey: input.recordKey,
|
|
71773
|
-
at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
71774
|
-
createCount,
|
|
71775
|
-
createByType,
|
|
71776
|
-
createNames,
|
|
71777
|
-
...core,
|
|
71778
|
-
...earlierBuild.length ? { earlierBuild } : {},
|
|
71779
|
-
...unrecordedDrafts.length ? { unrecordedDrafts } : {},
|
|
71780
|
-
fingerprint: previewFingerprint(core)
|
|
71781
|
-
};
|
|
71782
|
-
}
|
|
71783
|
-
function allowedWorkflowIds(allow) {
|
|
71784
|
-
return new Set(allow.filter((k) => k.startsWith("workflows:")).map((k) => k.slice("workflows:".length)));
|
|
71785
|
-
}
|
|
71786
|
-
function sharedPushAllowed(preview, confirm2, journaledBuild) {
|
|
71787
|
-
const confirmed = preview ? confirm2?.fingerprint === preview.fingerprint : journaledBuild;
|
|
71788
|
-
return confirmed && boardAllowed(preview, confirm2);
|
|
71789
|
-
}
|
|
71790
|
-
function approvedBoardId(preview, confirm2) {
|
|
71791
|
-
const board = preview?.change.find((e) => e.key.startsWith(BOARD_KEY_PREFIX));
|
|
71792
|
-
return board && confirm2 && confirm2.fingerprint === preview.fingerprint && confirm2.allow.includes(board.key) ? board.id : void 0;
|
|
71793
|
-
}
|
|
71794
|
-
function boardAllowed(preview, confirm2) {
|
|
71795
|
-
const board = preview?.change.find((e) => e.key.startsWith(BOARD_KEY_PREFIX));
|
|
71796
|
-
if (!board) return true;
|
|
71797
|
-
return Boolean(confirm2 && confirm2.fingerprint === preview.fingerprint && confirm2.allow.includes(board.key));
|
|
71798
|
-
}
|
|
71799
|
-
function confirmMatches(confirm2, preview) {
|
|
71800
|
-
if (!previewNamesLogins(preview)) return false;
|
|
71801
|
-
if (preview.earlierBuild?.length) return false;
|
|
71802
|
-
if (!confirm2 || confirm2.fingerprint !== preview.fingerprint || !Array.isArray(confirm2.allow)) return false;
|
|
71803
|
-
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
71804
|
-
return confirm2.allow.every((k) => changeKeys.has(k));
|
|
71805
|
-
}
|
|
71806
|
-
function previewsPath(base) {
|
|
71807
|
-
return (0, import_node_path15.join)(base, "build-previews.json");
|
|
71808
|
-
}
|
|
71809
|
-
function readPreviews(base) {
|
|
71810
|
-
try {
|
|
71811
|
-
const raw = JSON.parse((0, import_node_fs17.readFileSync)(previewsPath(base), "utf8"));
|
|
71812
|
-
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
71813
|
-
} catch {
|
|
71814
|
-
return {};
|
|
71815
|
-
}
|
|
71816
|
-
}
|
|
71817
|
-
function saveBuildPreview(preview, base = appDataDir()) {
|
|
71818
|
-
(0, import_node_fs17.mkdirSync)(base, { recursive: true, mode: 448 });
|
|
71819
|
-
const all = readPreviews(base);
|
|
71820
|
-
all[preview.recordKey] = preview;
|
|
71821
|
-
const file3 = previewsPath(base);
|
|
71822
|
-
const tmp = `${file3}.${process.pid}.tmp`;
|
|
71823
|
-
(0, import_node_fs17.writeFileSync)(tmp, JSON.stringify(all, null, 2), { mode: 384 });
|
|
71824
|
-
(0, import_node_fs17.renameSync)(tmp, file3);
|
|
71825
|
-
}
|
|
71826
|
-
function readBuildPreview(recordKey, base = appDataDir()) {
|
|
71827
|
-
const p = readPreviews(base)[recordKey];
|
|
71828
|
-
return p && p.v === 1 && typeof p.fingerprint === "string" && Array.isArray(p.reuse) && Array.isArray(p.change) ? p : void 0;
|
|
71829
|
-
}
|
|
71830
|
-
function readBuildConfirm(recordKey, base = appDataDir()) {
|
|
71831
|
-
try {
|
|
71832
|
-
const c = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path15.join)(base, "cockpit-state.json"), "utf8"))?.buildConfirms?.[recordKey];
|
|
71833
|
-
if (!c || typeof c !== "object") return void 0;
|
|
71834
|
-
const fingerprint2 = Reflect.get(c, "fingerprint");
|
|
71835
|
-
const allow = Reflect.get(c, "allow");
|
|
71836
|
-
const at = Reflect.get(c, "at");
|
|
71837
|
-
if (typeof fingerprint2 !== "string" || !Array.isArray(allow) || typeof at !== "string") return void 0;
|
|
71838
|
-
const by = Reflect.get(c, "by");
|
|
71839
|
-
const rawBefore = Reflect.get(c, "before");
|
|
71840
|
-
const before = rawBefore && typeof rawBefore === "object" ? Object.fromEntries(Object.entries(rawBefore).filter((e) => typeof e[1] === "string")) : {};
|
|
71841
|
-
return { fingerprint: fingerprint2, allow: allow.filter((k) => typeof k === "string"), at, ...typeof by === "string" ? { by } : {}, ...Object.keys(before).length ? { before } : {} };
|
|
71842
|
-
} catch {
|
|
71843
|
-
return void 0;
|
|
71844
|
-
}
|
|
71845
|
-
}
|
|
71846
|
-
function readBoardCreated(locationId2, base = appDataDir()) {
|
|
71847
|
-
try {
|
|
71848
|
-
const id2 = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path15.join)(base, "cockpit-state.json"), "utf8"))?.sharedBoardCreated?.[locationId2];
|
|
71849
|
-
return typeof id2 === "string" && id2 ? id2 : void 0;
|
|
71850
|
-
} catch {
|
|
71851
|
-
return void 0;
|
|
71852
|
-
}
|
|
71853
|
-
}
|
|
71854
|
-
function allowedChangesFrom(preview, confirm2) {
|
|
71855
|
-
return preview.change.filter((e) => confirm2.allow.includes(e.key)).map((e) => {
|
|
71856
|
-
const b = confirm2.before?.[e.key];
|
|
71857
|
-
return { type: e.type, name: e.name, ghlId: e.id, ...typeof b === "string" ? { before: b } : {} };
|
|
71858
|
-
});
|
|
71859
|
-
}
|
|
71860
|
-
async function readConfirmBefore(preview, allowed, readValue) {
|
|
71861
|
-
const out = {};
|
|
71862
|
-
for (const e of preview.change) {
|
|
71863
|
-
if (!allowed.includes(e.key) || !e.key.startsWith(BOARD_KEY_PREFIX)) continue;
|
|
71864
|
-
const v = await readValue(e.id);
|
|
71865
|
-
if (typeof v !== "string") throw new Error("Command OS could not read the current progress record in GoHighLevel, so it cannot record what an undo would put back. Nothing was confirmed. Try again.");
|
|
71866
|
-
out[e.key] = v;
|
|
71867
|
-
}
|
|
71868
|
-
return out;
|
|
71869
|
-
}
|
|
71870
|
-
function listedValue(raw, id2) {
|
|
71871
|
-
const listed = raw && typeof raw === "object" ? Reflect.get(raw, "customValues") : void 0;
|
|
71872
|
-
const hit = Array.isArray(listed) ? listed.find((v) => !!v && typeof v === "object" && Reflect.get(v, "id") === id2) : void 0;
|
|
71873
|
-
const value = hit && typeof hit === "object" ? Reflect.get(hit, "value") : void 0;
|
|
71874
|
-
return typeof value === "string" ? value : void 0;
|
|
71875
|
-
}
|
|
71876
|
-
function earlierBuildText(earlier, clientName) {
|
|
71877
|
-
const runs = [...new Set(earlier.map((e) => e.runId))];
|
|
71878
|
-
const names = earlier.map((e) => `"${e.name}"`).join(", ");
|
|
71879
|
-
return {
|
|
71880
|
-
why: `${clientName} already has ${earlier.length === 1 ? "an item" : `${earlier.length} items`} an earlier build of a different plan made: ${names}. Building now would build on top of ${earlier.length === 1 ? "it" : "them"}, which is how duplicates happen, so Build will not start. Undo the earlier build first, or stop here.`,
|
|
71881
|
-
undo: runs.map((r) => `Undo build run ${r} for ${clientName}`)
|
|
71882
|
-
};
|
|
71883
|
-
}
|
|
71884
|
-
var import_node_fs17, import_node_path15, BUILD_PAUSED, BUILD_PREVIEW_CHANGED, keyOf4, stableChange, BOARD_VALUE_NAME, BOARD_KEY_PREFIX, BOARD_CHANGE, PREVIEW_KIND;
|
|
71885
|
-
var init_build_confirm = __esm({
|
|
71886
|
-
"src/build-confirm.ts"() {
|
|
71887
|
-
"use strict";
|
|
71888
|
-
import_node_fs17 = require("node:fs");
|
|
71889
|
-
import_node_path15 = require("node:path");
|
|
71890
|
-
init_credentials_store();
|
|
71891
|
-
init_build_contract();
|
|
71892
|
-
init_build_facts();
|
|
71893
|
-
BUILD_PAUSED = "Build paused before writing anything. Open the Build step to see what it will create, what already in the account it will use, and what it would change, then confirm.";
|
|
71894
|
-
BUILD_PREVIEW_CHANGED = "What Build would use or change in the account is not what you confirmed (something in the account or the plan changed). Nothing was written. Look at the Build step again and confirm the new list.";
|
|
71895
|
-
keyOf4 = (type, id2) => `${type}:${id2}`;
|
|
71896
|
-
stableChange = (c) => c.startsWith("could not be read") ? "could not be read" : c;
|
|
71897
|
-
BOARD_VALUE_NAME = "Command OS Board";
|
|
71898
|
-
BOARD_KEY_PREFIX = "board:";
|
|
71899
|
-
BOARD_CHANGE = "the cockpit's own progress record: the cockpit would keep it up to date as this build moves through its steps. Left unticked, the cockpit keeps this client's progress on this computer only";
|
|
71900
|
-
PREVIEW_KIND = {
|
|
71901
|
-
users: "staff member",
|
|
71902
|
-
pipelines: "pipeline",
|
|
71903
|
-
customFields: "custom field",
|
|
71904
|
-
tags: "tag",
|
|
71905
|
-
customValues: "custom value",
|
|
71906
|
-
calendars: "calendar",
|
|
71907
|
-
forms: "form",
|
|
71908
|
-
workflows: "workflow",
|
|
71909
|
-
emails: "email",
|
|
71910
|
-
sms: "text message",
|
|
71911
|
-
emailTemplates: "email template",
|
|
71912
|
-
smsTemplates: "text message template"
|
|
71913
|
-
};
|
|
71914
|
-
}
|
|
71915
|
-
});
|
|
71916
|
-
|
|
71917
72010
|
// src/intake-to-build/cockpit-brief.ts
|
|
71918
72011
|
function briefFromAnswers(answers, questions, industry, briefId) {
|
|
71919
72012
|
if (!answers) return void 0;
|
|
@@ -72383,6 +72476,7 @@ __export(dashboard_exports, {
|
|
|
72383
72476
|
diffIntakeShape: () => diffIntakeShape,
|
|
72384
72477
|
diffShownItems: () => diffShownItems,
|
|
72385
72478
|
documentScopeFor: () => documentScopeFor,
|
|
72479
|
+
earlierBuildUndone: () => earlierBuildUndone,
|
|
72386
72480
|
engineRepair: () => engineRepair,
|
|
72387
72481
|
ensureEngineReady: () => ensureEngineReady,
|
|
72388
72482
|
fillBusinessType: () => fillBusinessType,
|
|
@@ -72625,14 +72719,20 @@ function setAccountIndustry(state2, locationId2, industry) {
|
|
|
72625
72719
|
if (!next.industry) delete next.industry;
|
|
72626
72720
|
return { ...state2, accounts: { ...state2.accounts ?? {}, [locationId2]: next } };
|
|
72627
72721
|
}
|
|
72628
|
-
function buildPreviewsFor(st) {
|
|
72722
|
+
function buildPreviewsFor(st, base) {
|
|
72629
72723
|
const out = {};
|
|
72630
72724
|
for (const rk of Object.keys(st.clients ?? {})) {
|
|
72631
|
-
const preview = readBuildPreview(rk);
|
|
72632
|
-
if (preview) out[rk] = { preview, confirmed: st.buildConfirms?.[rk]?.fingerprint === preview.fingerprint };
|
|
72725
|
+
const preview = readBuildPreview(rk, base);
|
|
72726
|
+
if (preview) out[rk] = { preview, confirmed: st.buildConfirms?.[rk]?.fingerprint === preview.fingerprint, ...earlierBuildUndone(rk, preview, base) ? { earlierUndone: true } : {} };
|
|
72633
72727
|
}
|
|
72634
72728
|
return out;
|
|
72635
72729
|
}
|
|
72730
|
+
function earlierBuildUndone(rk, preview, base) {
|
|
72731
|
+
const runs = new Set((preview.earlierBuild ?? []).map((e) => e.runId));
|
|
72732
|
+
if (!runs.size || !preview.at) return false;
|
|
72733
|
+
const done = new Set(readJournal(rk, base).flatMap((r) => r.kind === "revert" && r.complete === true && r.at > preview.at ? [r.runId] : []));
|
|
72734
|
+
return [...runs].every((id2) => done.has(id2));
|
|
72735
|
+
}
|
|
72636
72736
|
function scopeHashOf(sel) {
|
|
72637
72737
|
if (!sel) return contentHash2({ none: true });
|
|
72638
72738
|
if (sel.wholePreset) return contentHash2({ wholePreset: true });
|
|
@@ -73133,13 +73233,15 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
73133
73233
|
outcome.issues = [];
|
|
73134
73234
|
outcome.plain = { summary: BRIEF_NEEDS_INTAKE2, issues: [] };
|
|
73135
73235
|
}
|
|
73136
|
-
const
|
|
73236
|
+
const buildPause = stage === 3 && !outcome.ok && !scopeRefusal && buildPausedFor(rk, readCockpitState().buildConfirms, void 0, { since: run.startedAt });
|
|
73237
|
+
if (buildPause) run.paused = true;
|
|
73238
|
+
const noted = failureNoteFor(stage, outcome, run.lines, scopeRefusal || briefNeedsIntake || buildPause);
|
|
73137
73239
|
const failNote = noted !== null;
|
|
73138
73240
|
if (noted) Object.assign(outcome, noted);
|
|
73139
73241
|
if (stage === 3 && !outcome.summary && !outcome.issues?.length) {
|
|
73140
73242
|
try {
|
|
73141
73243
|
const st = readCockpitState();
|
|
73142
|
-
writeCockpitState({ ...st, outcomes: { ...st.outcomes ?? {}, [`${rk}:3`]: { at: (/* @__PURE__ */ new Date()).toISOString(), inputHash: runInputHash, hash: contentHash2({ inputHash: runInputHash, at: Date.now() }) } } });
|
|
73244
|
+
writeCockpitState({ ...st, outcomes: { ...st.outcomes ?? {}, [`${rk}:3`]: { at: (/* @__PURE__ */ new Date()).toISOString(), inputHash: runInputHash, hash: contentHash2({ inputHash: runInputHash, at: Date.now() }), ...buildPause ? { paused: true } : {} } } });
|
|
73143
73245
|
} catch {
|
|
73144
73246
|
}
|
|
73145
73247
|
}
|
|
@@ -73149,7 +73251,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
73149
73251
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
73150
73252
|
const save = (plain) => {
|
|
73151
73253
|
const st = readCockpitState();
|
|
73152
|
-
writeCockpitState({ ...st, outcomes: { ...st.outcomes ?? {}, [`${rk}:${stage}`]: { ok: outcome.ok, error: outcome.error, summary: outcome.summary, issues: outcome.issues, ...plain && { plain }, ...briefNeedsIntake && { reason: "intake-needed" }, at, inputHash: runInputHash, ...stage === 4 ? { buildHash: runBuildHash, verification: verifyRaw?.verification } : {}, hash: contentHash2({ inputHash: runInputHash, at, ok: outcome.ok, summary: outcome.summary, issues: outcome.issues }) } } });
|
|
73254
|
+
writeCockpitState({ ...st, outcomes: { ...st.outcomes ?? {}, [`${rk}:${stage}`]: { ok: outcome.ok, ...buildPause ? { paused: true } : {}, error: outcome.error, summary: outcome.summary, issues: outcome.issues, ...plain && { plain }, ...briefNeedsIntake && { reason: "intake-needed" }, at, inputHash: runInputHash, ...stage === 4 ? { buildHash: runBuildHash, verification: verifyRaw?.verification } : {}, hash: contentHash2({ inputHash: runInputHash, at, ok: outcome.ok, summary: outcome.summary, issues: outcome.issues }) } } });
|
|
73153
73255
|
};
|
|
73154
73256
|
save(scopeRefusal || briefNeedsIntake || failNote ? outcome.plain : void 0);
|
|
73155
73257
|
if (PLAIN_REPORT_STAGES.has(stage) && !scopeRefusal && !briefNeedsIntake && !failNote) {
|
|
@@ -78565,7 +78667,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78565
78667
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
78566
78668
|
const proClient = gate3.plan === "pro" ? new URL(req.url || "/", "http://x").searchParams.get("locationId") ?? "" : void 0;
|
|
78567
78669
|
const mine = activeRun2 && (proClient === void 0 || activeRun2.locationId === proClient);
|
|
78568
|
-
res.end(JSON.stringify(mine && activeRun2 ? { ...activeRun2, lines: memberProgress(activeRun2.lines, activeRun2.running, activeRun2.ok, activeRun2.capability ? void 0 : activeRun2.stage) } : activeRun2?.running ? { running: true, elsewhere: true, lines: [] } : { running: false, lines: [] }));
|
|
78670
|
+
res.end(JSON.stringify(mine && activeRun2 ? { ...activeRun2, lines: memberProgress(activeRun2.lines, activeRun2.running, activeRun2.ok, activeRun2.capability ? void 0 : activeRun2.stage, activeRun2.paused) } : activeRun2?.running ? { running: true, elsewhere: true, lines: [] } : { running: false, lines: [] }));
|
|
78569
78671
|
return;
|
|
78570
78672
|
}
|
|
78571
78673
|
if (req.method === "GET" && url === "/api/run/secrets") {
|