@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/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) || "the engine did not answer in the format every stage depends on"
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 result = [running ? "Command OS is working on this step." : ok === true ? "This step finished. Review the result below." : failed];
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 texting is registered and you switch them on.` : "";
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 = { "&amp;": "&", "&nbsp;": " ", "&quot;": '"', "&apos;": "'", "&lt;": "<", "&gt;": ">", "&mdash;": " ", "&ndash;": " " };
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, import_node_crypto17.randomUUID)()}.tmp`;
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, import_node_crypto17, import_zod76, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, decimalsOf, amountText, stripePanel, slackPanel, zoomPanel, calendarPanel;
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
- import_node_crypto17 = require("node:crypto");
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.1",
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">${esc(pkg.version)}</span></div>
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="${esc(productName())} home">${home}</a><span aria-hidden="true">/</span>${crumbLabel(active, opts.capability, opts.trail)}`;
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">${esc(t)}</span>` : `<span>${esc(t)}</span><span aria-hidden="true">/</span>`).join("");
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 ${esc(pkg.version)}</span></div>
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>${esc(title2)}</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 esc, 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;
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
- esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
46810
+ esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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="${esc(title2)}"` : ""}><span>${label2}</span>${badgeId ? `<span class="cds-side-badge" id="${badgeId}" hidden></span>` : ""}</a>`;
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, import_node_crypto18.createHash)("sha256").update(canonicalJson(value)).digest("hex");
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 import_node_crypto18, INTAKE_RECHECKED_SENTENCE;
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
- import_node_crypto18 = require("node:crypto");
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 = import_node_fs6.default.lstatSync(dir);
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 = [[import_node_path5.default.dirname(file3), false], [import_node_path5.default.join(import_node_path5.default.dirname(file3), STAGING_DIR_NAME), true]];
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 (!import_node_fs6.default.existsSync(dir)) {
46471
- import_node_fs6.default.mkdirSync(dir, { recursive: true, mode: 448 });
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
- import_node_fs6.default.chmodSync(dir, 448);
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 = import_node_path5.default.join(import_node_path5.default.dirname(file3), STAGING_DIR_NAME, import_node_path5.default.basename(file3));
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 = import_node_fs6.default.openSync(stage, "wx", 384);
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() - import_node_fs6.default.statSync(stage).mtimeMs;
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
- import_node_fs6.default.unlinkSync(stage);
47138
+ import_node_fs8.default.unlinkSync(stage);
46499
47139
  } catch {
46500
47140
  }
46501
- fd = import_node_fs6.default.openSync(stage, "wx", 384);
47141
+ fd = import_node_fs8.default.openSync(stage, "wx", 384);
46502
47142
  }
46503
47143
  try {
46504
47144
  let opened;
46505
47145
  try {
46506
- import_node_fs6.default.writeSync(fd, text3);
46507
- import_node_fs6.default.fsyncSync(fd);
46508
- opened = import_node_fs6.default.fstatSync(fd);
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
- import_node_fs6.default.closeSync(fd);
47150
+ import_node_fs8.default.closeSync(fd);
46511
47151
  }
46512
- if (!sameFile(opened, import_node_fs6.default.lstatSync(stage))) throw new Error("cache: the staged file was replaced before it was installed");
46513
- import_node_fs6.default.renameSync(stage, file3);
46514
- if (!sameFile(opened, import_node_fs6.default.lstatSync(file3))) throw new Error("cache: the installed file is not the one written");
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
- import_node_fs6.default.unlinkSync(stage);
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 import_node_path5.default.join(appDataDir2, CACHE_FILE_NAME);
47165
+ return import_node_path7.default.join(appDataDir2, CACHE_FILE_NAME);
46526
47166
  }
46527
47167
  function removeCacheFileAtRest(file3) {
46528
47168
  try {
46529
- import_node_fs6.default.unlinkSync(file3);
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, import_node_crypto19, import_node_fs6, import_node_path5, 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;
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
- import_node_crypto19 = require("node:crypto");
46600
- import_node_fs6 = __toESM(require("node:fs"));
46601
- import_node_path5 = __toESM(require("node:path"));
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, import_node_crypto19.createHash)("sha256").update(s).digest("hex");
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
- import_node_fs6.default.unlinkSync(this.deps.file);
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 = import_node_fs6.default.readFileSync(file3, "utf8");
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
- import_node_fs6.default.unlinkSync(file3);
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(import_node_fs6.default.readFileSync(file3, "utf8"));
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 = import_node_fs6.default.statSync(file3);
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(import_node_fs6.default.readFileSync(file3, "utf8"));
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 import_node_path6.default.join(base, "secret-shopper");
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 import_node_path6.default.join(shopperDir(base), locationId2, `${runId}.json`);
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
- import_node_fs7.default.mkdirSync(import_node_path6.default.dirname(file3), { recursive: true, mode: 448 });
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
- import_node_fs7.default.writeFileSync(tmp, JSON.stringify(run, null, 1), { mode: 384 });
48071
- import_node_fs7.default.renameSync(tmp, file3);
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(import_node_fs7.default.readFileSync(runPath(locationId2, runId, base), "utf8"));
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 = import_node_path6.default.join(shopperDir(base), locationId2);
48722
+ const dir = import_node_path8.default.join(shopperDir(base), locationId2);
48083
48723
  let names = [];
48084
48724
  try {
48085
- names = import_node_fs7.default.readdirSync(dir).filter((n) => n.endsWith(".json"));
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(import_node_fs7.default.readFileSync(import_node_path6.default.join(dir, n), "utf8")));
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 = import_node_crypto20.default.randomUUID().replace(/-/g, "").slice(0, 8);
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
- import_node_fs7.default.mkdirSync(import_node_path6.default.dirname(lock), { recursive: true, mode: 448 });
48377
- const token2 = import_node_crypto20.default.randomUUID();
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 = import_node_fs7.default.openSync(lock, "wx", 384);
49021
+ const fd = import_node_fs9.default.openSync(lock, "wx", 384);
48382
49022
  try {
48383
- import_node_fs7.default.writeSync(fd, mine);
49023
+ import_node_fs9.default.writeSync(fd, mine);
48384
49024
  } finally {
48385
- import_node_fs7.default.closeSync(fd);
49025
+ import_node_fs9.default.closeSync(fd);
48386
49026
  }
48387
49027
  return () => {
48388
49028
  try {
48389
- if (import_node_fs7.default.readFileSync(lock, "utf8") === mine) import_node_fs7.default.unlinkSync(lock);
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 = import_node_fs7.default.readFileSync(lock, "utf8");
48399
- age = Date.now() - import_node_fs7.default.statSync(lock).mtimeMs;
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 (import_node_fs7.default.readFileSync(lock, "utf8") === held2) import_node_fs7.default.unlinkSync(lock);
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 import_node_fs7, import_node_path6, import_node_crypto20, MAX_EXTEND_MINUTES, asObj8, asArr4, str10, SAFE_ID, notFound;
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
- import_node_fs7 = __toESM(require("node:fs"));
48515
- import_node_path6 = __toESM(require("node:path"));
48516
- import_node_crypto20 = __toESM(require("node:crypto"));
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, import_node_crypto21.default.randomUUID());
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
- import_node_fs8.default.mkdirSync(import_node_path7.default.dirname(f), { recursive: true, mode: 448 });
48729
- import_node_fs8.default.appendFileSync(f, JSON.stringify(e) + "\n", { encoding: "utf8", mode: 384 });
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 = import_node_fs8.default.readFileSync(journalFile(base), "utf8").split("\n").filter(Boolean);
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 import_node_fs8, import_node_path7, import_node_crypto21, asObj9, asArr5, str11, FF_PREFIX, ALLOWED, hostOf, normalizeHost, newToken, TOKEN_NAME, tokenPrefix, DAY_NAMES, remapSelf, journalFile, SWEEP_MIN_AGE_MS;
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
- import_node_fs8 = __toESM(require("node:fs"));
48894
- import_node_path7 = __toESM(require("node:path"));
48895
- import_node_crypto21 = __toESM(require("node:crypto"));
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 += (import_node_crypto21.default.randomBytes(1)[0] % 36).toString(36);
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) => import_node_path7.default.join(shopperDir(base), "fast-forward-journal.jsonl");
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 ? "" : ""}. ${esc2(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">${esc2(caption)}</div></div>`;
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}">${esc2(new Date(r.at).toISOString().slice(11, 16))}</time></span>${dot(tone)}<span class="what">${esc2(r.stepName)}<small>${esc2(r.detail)}</small></span>${word ? chip(tone, word) : ""}</li>`;
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", "", esc2(a.unreadable ?? "The step log could not be read.")));
48962
- const rows2 = a.problems.map((p) => `<li><span class="ttl">${esc2(p.stepName)}<span class="sub">${esc2(p.detail)}</span><span class="sub"><b>The fix:</b> ${esc2(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>`);
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(esc2).join("<br>")) : "";
48973
- const lists = [...byWf.entries()].map(([name2, steps]) => `<details${a.problems.length ? " open" : ""}><summary class="cds-link">${esc2(name2)} (${steps.length} step${steps.length === 1 ? "" : "s"})</summary>${capped(steps.map(stepRow), tl)}</details>`).join("");
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 esc2(quote2);
49618
+ if (!highlight) return esc3(quote2);
48979
49619
  const i = quote2.indexOf(highlight);
48980
- if (i < 0) return esc2(quote2);
48981
- return `${esc2(quote2.slice(0, i))}<mark>${esc2(highlight)}</mark>${esc2(quote2.slice(i + highlight.length))}`;
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">${esc2(p.title)}<span class="sub">What the lead got: "${marked(p.quote, p.highlight)}"</span><span class="sub">Sent by: ${p.sentBy ? esc2(p.sentBy) : "Not matched to a planned step"}</span><span class="sub"><b>The fix:</b> ${esc2(p.fix)}</span></span><span class="end">${chip("bad", CHIP[p.kind])}${chip("info", CHANNEL[p.channel] ?? "Message")}${chip("", mins(p.minute))}</span></li>`;
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: "${esc2(t.subject ?? t.preview)}"` : `${CHANNEL[t.channel] ?? "Message"}: "${esc2(t.preview)}"`;
48989
- return `<li><span class="t">${mins(t.minute)}</span>${dot(tone)}<span class="what">${what}<small>${esc2(t.sentBy ?? (t.source === "workflow" ? "Sent by a workflow" : t.source ? `Sent by ${t.source}` : ""))}</small></span>${chip(tone, word)}</li>`;
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", `${esc2(run.leadEmail)} (your address, not a customer's)`],
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 ? `. ${esc2(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", esc2(run.error ?? "Unknown error"), action),
49004
- rest: callout("warn", "", esc2(run.cleanup?.detail ?? "No test lead was sent."))
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: ${esc2(run.fastForward.copies.map((c) => `"${c.copyName}"`).join(", "))}. Delete them in GoHighLevel.`) : ""}${run.cleanup && !run.cleanup.removed ? callout("warn", "Test lead:", ` ${esc2(run.cleanup.detail)}`) : ""}`;
49014
- const summary = `<div class="sum"><div class="cds-stack"><p>Finished ${when(run.finishedAt ?? run.startedAt)}. ${esc2(card.coverage.split(". Not checked")[0])}.</p>${leftovers}<div class="cds-row cds-no-print"><a class="cds-btn ghost" href="${href}&amp;print=1" target="_blank" rel="noopener">Save as PDF</a></div></div>
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}&amp;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", esc2(clientName) || "This check", "", summary)
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. ${esc2(run.entryNote)}`, action);
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.", esc2(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, "")}"`, `${esc2(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.`) : "";
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.", ` ${esc2(card.reason)}`) : "";
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:", ` ${esc2(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.`) : "";
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: ${esc2(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>`)
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 ? `${esc2(m.label)}: GoHighLevel was still holding the test lead at ${esc2(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.` : `${esc2(m.label)}, due after the window closed.`),
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>${esc2(c)}</span></li>`).join(""))}
49095
- <p>${esc2(plan)}</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:", ` ${esc2(run.cleanup.detail + ffLine)} Anything that looked like a password or key in a message was hidden before it reached this page.`) : "";
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="${esc2(d.ownerEmail ?? "")}" autocomplete="email" aria-describedby="owner-email-hint"></div>
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 ${esc2(d.client.name)}`, "Sends one test lead, then lists the contacts that carry its address", form, ` id="start"`, "cds-no-print");
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="${esc2(a.locationId)}"${d.client?.locationId === a.locationId ? ' selected=""' : ""}>${esc2(a.name)}</option>`).join("")}</select></div><noscript><button class="cds-btn ghost" type="submit">Open</button></noscript></form>`;
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 ${esc2(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);
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 esc2, chip, pageHead, panel, callout, statTile, list2, tl, OUTCOME, PROBLEM_CHIP, dot, CHIP, CHANNEL, mins, when, reportHref, CSS;
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
- esc2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
49306
- chip = (tone, text3) => `<span class="cds-chip${tone ? ` ${tone}` : ""}">${esc2(text3)}</span>`;
49945
+ esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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}` : ""}">${esc2(String(value))}</div><div class="lbl">${esc2(label2)}</div></div>`;
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="${esc3(src)}" alt="${esc3(b.name)}">` : "";
49591
- return `<div class="rp-brand">${logo}${esc3(b.name)} <span>${esc3(product)}</span></div>`;
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="${esc3(k)}" value="${esc3(v)}">`).join("");
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">${esc3(n)}</p>`).join("")}</div>` : "";
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="${esc3(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}`;
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, esc3, BRAND_LAYOUT_CSS, BRAND_LOGO_JS, BRAND_SAVE_UNREACHABLE;
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
- esc3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
50293
+ esc4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 ? "" : ""}. ${esc4(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">${esc4(caption)}</div>` : ""}</div>`;
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">${esc4(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>`;
49676
- }).join("")}</div><div class="cds-src">${esc4(source)}</div></div>`;
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 ? `&minus;${d.points}` : "0"}</span><span>${esc4(d.text)}${d.cap !== void 0 && d.points ? `<span class="cap">${esc4(capText(d.cap))}</span>` : ""}${d.causedBy ? `<span class="note">${esc4(d.causedBy)}</span>` : ""}</span></li>`).join("")}<li class="total"><span class="pts">&minus;${total2}</span><span>Points lost in total${capped2 !== void 0 ? `, then capped at ${capped2}` : ""}</span></li></ul>`;
50320
+ return `<ul class="cds-ded">${items.map((d) => `<li class="${d.points ? "" : "excused"}"><span class="pts">${d.points ? `&minus;${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">&minus;${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">${esc4(r.stepName)}${r.detail ? `<small>${esc4(r.detail)}</small>` : ""}</span>${chip2(tone === "wait" ? "" : tone, word)}</div>`;
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">${esc4(offer?.why ?? why ?? "This one needs a decision a button cannot make, so change it in GoHighLevel.")}</p>`;
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">${esc4(st.technical)}</pre></details>` : "";
49696
- const state2 = !st ? "" : `<div class="cds-row rp-fixstate">${chip2(tone, word)}<span class="rp-soon">${esc4(
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-${esc4(key2)}">${esc4(inp.label)}</label><input class="cds-input" id="fx-${esc4(key2)}" type="${inp.kind === "url" ? "url" : "text"}" value="${esc4(inp.value ?? "")}" autocomplete="off"><span class="hint">${esc4(inp.hint)}</span><div class="cds-row"><button class="cds-btn" type="button" data-act="preview" disabled>Show the change</button></div></div>` : "";
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="${esc4(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>`;
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>${esc4(title2)}</h4>${chip2("bad", `\u2212${points} points`)}</div>${detail ? `<p>${esc4(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>${esc4(after)}</div></div>${controls}</div>`;
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 ? esc4(quote2) : `${esc4(quote2.slice(0, i))}<mark>${esc4(highlight)}</mark>${esc4(quote2.slice(i + highlight.length))}`;
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(`"${esc4(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" });
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, esc4(`"${p.stepName}" in ${p.workflowName}`), p.fix, controls);
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.", esc4(`${d}: draft`), "Publish the workflow, then run the check again", fixControls(fx, void 0, DRAFT_WHY));
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="${esc4(run.locationId)}" data-run="${esc4(run.runId)}"` : ""}><div class="rp-head"><div><div class="rp-label">Fix these</div><h2>${esc4(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>`;
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(esc4).join("<br>"), { tone: "warn" }) : ""}${[...byWf.values()].map((w) => `${multi ? `<div class="rp-tag">${esc4(w.name)}</div>` : ""}${workflowFlow([...w.steps.values()])}`).join("")}</div>`;
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">${esc4(stepName)}${problem ? `<small>"${esc4(problem.quote)}"</small>` : ""}</span>${chip2(tone, t.problems.length ? t.problems[0] === "blank_field" ? "Blank name" : "Problem" : "Arrived")}</li>`;
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(`${esc4(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(esc4(cleanupText), { tone: cleanedUp ? "info" : "warn", title: cleanedUp ? "Cleaned up" : leftCopies ? "Not cleaned up" : "Test lead" })}</section>`;
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>${esc4(`${product} \xB7 ${opts.clientName}`)}</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="${esc4(opts.backHref)}">Back to ${esc4(brand.mode === "standard" ? OWN_PRODUCT : "the audit")}</a>` : ""}<button class="cds-btn" type="button" id="save-pdf">Save as PDF</button></div><!--/rp-tools-->
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 ${esc4(product)} \xB7 ${esc4(opts.clientName)}</div><h1>${esc4(h1)}</h1><p class="rp-sub">${esc4(sub)}</p><p class="rp-meta">${esc4(meta)}</p><p class="rp-meta">${esc4(run.entryNote)}</p></div><div class="rp-ringbox">${gradeRing(card.score, ringCaption, true)}</div></section>
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>${esc4(source)}</span><span>${esc4(brand.footer)}</span></footer>
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 esc4, n2w, localTime2, gcls, chip2, statTile2, callout2, OUTCOME2, FIX_STATE, FIX_SCRIPT, LAYOUT_CSS, OWN_PRODUCT, NEUTRAL_PRODUCT;
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
- esc4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
50483
+ esc5 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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}">${esc4(text3)}</span>`;
49848
- statTile2 = (value, label2, tone = "") => `<div class="cds-stat"><div class="big ${tone}">${esc4(String(value))}</div><div class="lbl">${esc4(label2)}</div></div>`;
49849
- callout2 = (inner, opts) => `<div class="cds-callout ${opts.quote ? "quote" : ""} ${opts.tone ?? ""}">${opts.title ? `<span class="ttl">${esc4(opts.title)}</span>` : ""}${inner}${opts.cite ? `<cite>${esc4(opts.cite)}</cite>` : ""}</div>`;
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}])${esc5(parts.word)}${GAP}*(?:\\{\\{[^{}]*\\}\\}${GAP}*)+${esc5(parts.punct)}`, "gu");
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, "&amp;")])].sort((x, y) => y.length - x.length);
50148
- pattern = new RegExp(variants.map(esc5).join("|"), "g");
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(esc5).join("|"), "g");
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 import_node_crypto22, import_zod77, ActionSchema3, asWorkflowActions, asObj10, asArr6, str12, NEEDS_A_PERSON, sha2, templatesHash2, find, clone, escHtml, readable, GAP, esc5, refs2, BRANCHING, MAX_STEPS, pretty, CORE;
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
- import_node_crypto22 = __toESM(require("node:crypto"));
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) => import_node_crypto22.default.createHash("sha256").update(s).digest("hex");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
50538
51178
  readable = (v, html2) => html2 ? htmlToText(v) : v;
50539
51179
  GAP = "(?:[ \\t\\u00a0]|&nbsp;)";
50540
- esc5 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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 import_node_path8.default.join(shopperDir(base), locationId2, "fixes", `${runId}.json`);
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(import_node_fs9.default.readFileSync(ledgerPath(locationId2, runId, base), "utf8")));
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
- import_node_fs9.default.mkdirSync(import_node_path8.default.dirname(file3), { recursive: true, mode: 448 });
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
- import_node_fs9.default.writeFileSync(tmp, JSON.stringify(records, null, 1), { mode: 384 });
50590
- import_node_fs9.default.renameSync(tmp, file3);
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 ?? (() => import_node_crypto23.default.randomUUID()) });
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: import_node_crypto23.default.randomUUID(),
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 import_node_fs9, import_node_path8, import_node_crypto23, asObj11, asArr7, str13, PREVIEW_TTL_MS2, previews, writing, SAFE_ID2, newestFirst, NO_AUTO_ROLLBACK, templatesOf2, firstLine2, view;
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
- import_node_fs9 = __toESM(require("node:fs"));
50840
- import_node_path8 = __toESM(require("node:path"));
50841
- import_node_crypto23 = __toESM(require("node:crypto"));
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 ${esc6(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>
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 esc6;
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
- esc6 = (t) => t.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
52087
+ esc7 = (t) => t.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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>${esc7(entry.gives)}</p>${how(connectorInstructions(entry, "html"), c.id, false)}</section>`;
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>${esc7(entry.gives)}</p>
52313
+ <p>${esc8(entry.gives)}</p>
51674
52314
  ${statusKv()}
51675
- <p class="next-step"${nextStep(c) ? "" : " hidden"}>${esc7(nextStep(c))}</p>
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"}>${esc7(CALENDAR_SETUP_MESSAGE)}</p>` : ""}
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="${esc7(c.label || "")}"></div><div class="cds-row"><button class="cds-btn ghost" type="submit">Connect</button></div></form>`}
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>${esc7(c.setupDetail || "")}</p></details>` : ""}
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: ${esc7(unreadable.join(", "))}.` : "All saved connections could be read."}</dd></dl></section>
51707
- <nav class="cds-jump" aria-label="Jump to system">${cards.map((c) => `<a href="#${c.id}">${esc7(c.name)}</a>`).join("")}</nav>
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 esc7, needsYou, optional, progressText, alsoConnect, statusKv, keyDetails, keyWord, nextStep, chip3, head, how, zoomIdFields, needsRow;
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
- esc7 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
52482
+ esc8 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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}">${esc7(c.name)}</a>`);
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>${esc7(c.keyEndsWith || "")}</dd></dl></details>`;
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">${esc7(c.name)}</h2></div><div class="cds-row">${chips}</div></div>`;
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}">${esc7(c.name)}<span class="sub">${esc7(ask)}</span></a></li>`;
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, import_node_fs10.readFileSync)((0, import_node_path9.join)(base, "cockpit-state.json"), "utf8"));
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 import_node_fs10, import_node_path9, DEFAULT_ACCENT;
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
- import_node_fs10 = require("node:fs");
52179
- import_node_path9 = require("node:path");
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>${esc8(i)}</li>`).join("")}</ul>` : `<p class="intro">None recorded.</p>`;
52337
- const block = (title2, intro, body) => `<h3 class="dh">${esc8(title2)}</h3><p class="intro">${esc8(intro)}</p>${body}`;
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>${esc8(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>`;
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">${esc8(latest.summary)}</p>` : ""}${latest.issues?.length ? `<ul class="issues">${latest.issues.map((i) => `<li>${esc8(i)}</li>`).join("")}</ul>` : `<p class="intro">Nothing needed your attention this time.</p>`}</section>` : "";
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> ${esc8(scrubForClient(it.resolvedBy ?? "") || "not stated")}.${it.roughly ? ` <b>Roughly when:</b> ${esc8(scrubForClient(it.roughly))}.` : ""}</p>` : "";
52361
- const body = it.sentence ? `<p>${esc8(it.sentence)}</p>` : "";
52362
- return `<article class="item ${cls}"><span class="pill">${esc8(CLIENT_STATE_LABEL[it.state])}</span><h3>${esc8(it.title)}</h3>${body}${detail2}</article>`;
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>${esc8(title2)}</h2><p class="intro">${esc8(intro)}</p>${list4.map(entry).join("\n")}</section>` : "";
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>${esc8(detail ? `Account detail, for your agency: ${input.accountName}` : statusPageTitle(input))}</title>
52369
- <meta name="description" content="${esc8(`What is live in the ${input.accountName} account right now.`)}">
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">${esc8(agency || input.accountName)}</span>
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="${esc8(input.logoDataUri)}" alt="${esc8(agency ?? "")}">` : ""}${agency ? `<span class="agency">${esc8(agency)}</span>` : ""}<span class="for">Prepared for<br><b>${esc8(input.accountName)}</b></span></div>` : ""}
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>${esc8(input.accountName)}</h1>
52492
- <p class="stamp">${esc8(revLine)}</p>
52493
- <div class="verdict"><b>${esc8(headline3)}</b><span>${esc8(sub)}</span></div>
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>${esc8(n)}</li>`).join("")}</ol></section>` : ""}
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>${esc8(footLeft)}</span>${input.contact ? `<span>Questions? ${input.contact.href ? `<a href="${esc8(input.contact.href)}">${esc8(input.contact.label)}</a>` : esc8(input.contact.label)}</span>` : ""}</div>
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, esc8;
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
- [/\bworkflow(s)?\b/gi, "automation$1"],
52568
- [/\bcustom value(s)?\b/gi, "saved detail$1"],
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
- esc8 = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53256
+ esc9 = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 = { "&amp;": "&", "&nbsp;": " ", "&quot;": '"', "&apos;": "'", "&lt;": "<", "&gt;": ">", "&mdash;": " ", "&ndash;": " " };
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="${esc9(url)}">Open your handoff</a></p>` };
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 esc9;
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
- esc9 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
53519
+ esc10 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[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
- f("winback") ? "Old leads and past clients get a win-back message." : "",
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>${esc10(h)}</th>`).join("")}</tr></thead><tbody>${rows2.map((r) => `<tr>${r.map((c) => `<td>${esc10(c)}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
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">${esc10(o.problemSource)}</p>` : ""}</section>`);
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>${esc10(s.title)}</b>${esc10(s.text)}</div>`).join("")}</div>${o.afterSteps ? `<p>${esc10(o.afterSteps)}</p>` : ""}</section>`);
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>${esc10(o.afterSteps)}</p></section>`);
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>${esc10(s)}</span>`).join("")}</div></section>`);
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>${esc10(o.bought.workingLabel)}</th><th>Being set up next</th></tr></thead><tbody><tr><td>${esc10(o.bought.working.join(" \xB7 ") || "Nothing yet")}</td><td>${esc10(o.bought.next.join(" \xB7 ") || "Nothing else")}</td></tr></tbody></table></section>`);
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>${esc10(r)}</li>`).join("")}</ul>` : `<p>${esc10(NOTHING_NEEDED)}</p>`}</section>`);
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>${esc10(ownerPageTitle(o))}</title>
55978
- <meta name="description" content="${esc10(`What the new client system does for ${o.accountName}.`)}">
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">${esc10(agency || o.accountName)}</span>
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="${esc10(o.agency.logoDataUri)}" alt="">` : ""}${agency ? esc10(agency) : ""}</span><span>${esc10(prepared)}</span></div>
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">${esc10(o.summary)}</p>
56513
+ <p class="lead">${esc11(o.summary)}</p>
56054
56514
  ${sections.join("\n")}
56055
- <div class="foot"><p>${esc10([ownerContactLine(o), OWNER_UPDATES_LINE].filter(Boolean).join(" "))}</p>${stamp ? `<p class="stamp">${esc10(stamp)}</p>` : ""}</div>
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, esc10;
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
- reactivation: "A win-back message to your existing list",
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 your old list",
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
- esc10 = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
56668
+ esc11 = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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(esc11).join(" \xB7 ")}</p>`;
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 esc11(s) {
56840
+ function esc12(s) {
56379
56841
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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, import_node_fs12.readFileSync)(file(id2, base), "utf8"));
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, import_node_fs12.mkdirSync)((0, import_node_path11.join)(base, "workflow-repairs"), { recursive: true, mode: 448 });
56735
- (0, import_node_fs12.writeFileSync)(file(locationId2, base), JSON.stringify([...readWorkflowRepairs(locationId2, base).filter((r) => r.workflowId !== workflowId), repair]), { mode: 384 });
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 import_node_fs12, import_node_path11, file;
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
- import_node_fs12 = require("node:fs");
56743
- import_node_path11 = require("node:path");
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, import_node_path11.join)(base, "workflow-repairs", encodeURIComponent(id2) + ".json");
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[keyOf3(projectRef, locationId2)];
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, [keyOf3(rec2.projectRef, rec2.locationId)]: rec2 }, result: "ok" })),
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[keyOf3(projectRef, locationId2)];
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, keyOf3, REVIEWED, SendModeRequest;
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
- keyOf3 = (projectRef, locationId2) => `${projectRef}/${locationId2}`;
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="${esc12(a.locationId)}"${a.locationId === data.client?.locationId ? " selected" : ""}>${esc12(a.name)}</option>`).join("");
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 esc12;
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
- esc12 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
60108
+ esc13 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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, import_node_path12.isAbsolute)(script) || import_node_path12.win32.isAbsolute(script))) return "foreign";
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, import_node_fs13.realpathSync)(script);
60699
- if (!(0, import_node_fs13.statSync)(real).isFile()) return "foreign";
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, import_node_path12.basename)(real) !== LAUNCHER) return "foreign";
61184
+ if ((0, import_node_path13.basename)(real) !== LAUNCHER) return "foreign";
60704
61185
  try {
60705
- const manifest = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path12.join)((0, import_node_path12.dirname)(real), "package.json"), "utf8"));
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, import_node_fs13.realpathSync)(script);
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, import_node_path12.isAbsolute)(script2) || import_node_path12.win32.isAbsolute(script2)) && /(?:^|\/)dist\/index\.js$/.test(script2.replace(/\\/g, "/"))) {
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, import_node_fs13.readFileSync)((0, import_node_path12.join)((0, import_node_path12.dirname)((0, import_node_path12.dirname)(script2.replace(/\\/g, "/"))), "package.json"), "utf8"));
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 import_node_fs13, import_node_path12, import_node_util, object2, WIZARD_MANAGED_ENV, LAUNCHER, SHELLS, OUR_PACKAGE_ARG, SECRET_FLAG_WORDS, isSecretFlag, NAME_VALUE, KNOWN_KEY, HIDDEN;
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
- import_node_fs13 = require("node:fs");
60858
- import_node_path12 = require("node:path");
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, import_node_fs14.existsSync)(credentialsPath());
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, import_node_fs14, GHL_SERVER_NAME, CLAUDE_CODE_ADD_ARGS, CLAUDE_CODE_INSTALL_LINE, CLAUDE_CODE_ADD_HELP, OURS, defaultRunner;
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
- import_node_fs14 = require("node:fs");
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, import_node_path13.join)(appDataDir(), "design-runs");
61404
- (0, import_node_fs15.mkdirSync)(dir, { recursive: true });
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, import_node_path13.join)(designRunsDir(), id2);
61426
- (0, import_node_fs15.mkdirSync)(outDir, { recursive: true });
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, import_node_path13.join)(outDir, "run.log");
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, import_node_fs15.appendFileSync)(log, text3);
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, import_node_path13.join)(job.outDir, "run.json");
61473
- if (!(0, import_node_fs15.existsSync)(runJson)) return shown;
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, import_node_fs15.readFileSync)(runJson, "utf8")) };
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, import_node_fs15.readFileSync)((0, import_node_path13.join)(job.outDir, "run.json"), "utf8"));
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, import_node_fs15.realpathSync)(job.outDir);
61487
- const file3 = (0, import_node_fs15.realpathSync)((0, import_node_path13.isAbsolute)(run.file) ? run.file : (0, import_node_path13.join)(job.outDir, run.file));
61488
- return file3.startsWith(root + import_node_path13.sep) ? file3 : null;
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, import_node_fs15, import_node_path13, import_node_crypto29, SLUG2, MODEL, JOINED_FLAG, jobs, children, CHATTER, PROBLEM2, ABS_PATH;
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
- import_node_fs15 = require("node:fs");
61514
- import_node_path13 = require("node:path");
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
- if(bp&&!bp.confirmed&&!live){bsPause(c,state,bp.preview,box);return}
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
- box.appendChild(w);return}
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, import_node_path14.join)((0, import_node_os.homedir)(), "Documents", "assessments");
71526
- (0, import_node_fs16.mkdirSync)(dir, { recursive: true });
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, import_node_path14.join)(dir, `${slug3}-${(0, import_node_crypto31.randomBytes)(4).toString("hex")}.html`);
71529
- (0, import_node_fs16.writeFileSync)(file3, html2, "utf8");
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, import_node_fs16, import_node_os, import_node_path14, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema, PIPELINE_READBACK_WAITS_MS;
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
- import_node_fs16 = require("node:fs");
71857
+ import_node_fs17 = require("node:fs");
71566
71858
  import_node_os = require("node:os");
71567
- import_node_path14 = require("node:path");
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 noted = failureNoteFor(stage, outcome, run.lines, scopeRefusal || briefNeedsIntake);
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") {