@elitedcs/ghl-mcp 3.94.0 → 3.94.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/index.js +460 -170
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1717,12 +1717,12 @@ function looksLikeCredential(value) {
|
|
|
1717
1717
|
function maskUrl(url, rec2 = newRecorder(), path67 = []) {
|
|
1718
1718
|
let out = url.replace(USERINFO_RE, (_m, pre, pass, at) => `${pre}${maskCredential(pass, rec2, path67, "@")}${at}`);
|
|
1719
1719
|
const seen = /* @__PURE__ */ new Map();
|
|
1720
|
-
out = out.replace(QUERY_RE, (m,
|
|
1720
|
+
out = out.replace(QUERY_RE, (m, sep7, name2, val) => {
|
|
1721
1721
|
const decoded2 = decodeName(name2);
|
|
1722
1722
|
const n = seen.get(decoded2) ?? 0;
|
|
1723
1723
|
seen.set(decoded2, n + 1);
|
|
1724
1724
|
if (!val || !isSensitiveName(decoded2)) return m;
|
|
1725
|
-
return `${
|
|
1725
|
+
return `${sep7}${name2}=${maskCredential(val, rec2, path67, `?${decoded2}#${n}`)}`;
|
|
1726
1726
|
});
|
|
1727
1727
|
return out;
|
|
1728
1728
|
}
|
|
@@ -1911,7 +1911,7 @@ function redactText(text3) {
|
|
|
1911
1911
|
out = out.replace(/\b(Bearer|Basic)(\s+)([A-Za-z0-9._~+/=-]{8,})/g, (_m, s, sp, v) => `${s}${sp}${maskToken(v)}`);
|
|
1912
1912
|
out = out.replace(
|
|
1913
1913
|
/\b([A-Za-z0-9_-]*(?:key|token|secret|auth|signature|passw)[A-Za-z0-9_-]*)(\s*[:=]\s*)([^\s,;"'&<>]{8,})/gi,
|
|
1914
|
-
(m, name2,
|
|
1914
|
+
(m, name2, sep7, val) => MASK_PRESENT_RE.test(val) || /^(bearer|basic)$/i.test(val) || !isSensitiveName(name2) ? m : `${name2}${sep7}${maskToken(val)}`
|
|
1915
1915
|
);
|
|
1916
1916
|
return out;
|
|
1917
1917
|
}
|
|
@@ -2020,11 +2020,11 @@ function restoreMaskedSecrets(incoming, current) {
|
|
|
2020
2020
|
};
|
|
2021
2021
|
let out = str16.replace(USERINFO_RE, (m, pre, pass, at) => MASK_PRESENT_RE.test(pass) ? `${pre}${whole(pass, "@")}${at}` : m);
|
|
2022
2022
|
const seen = /* @__PURE__ */ new Map();
|
|
2023
|
-
out = out.replace(QUERY_RE, (m,
|
|
2023
|
+
out = out.replace(QUERY_RE, (m, sep7, name2, val) => {
|
|
2024
2024
|
const decoded2 = decodeName(name2);
|
|
2025
2025
|
const n = seen.get(decoded2) ?? 0;
|
|
2026
2026
|
seen.set(decoded2, n + 1);
|
|
2027
|
-
return MASK_PRESENT_RE.test(val) ? `${
|
|
2027
|
+
return MASK_PRESENT_RE.test(val) ? `${sep7}${name2}=${whole(val, `?${decoded2}#${n}`)}` : m;
|
|
2028
2028
|
});
|
|
2029
2029
|
if (MASK_PRESENT_RE.test(out)) out = whole(out, "=");
|
|
2030
2030
|
return out;
|
|
@@ -10998,18 +10998,18 @@ async function readAgent(client, agentId) {
|
|
|
10998
10998
|
return a;
|
|
10999
10999
|
}
|
|
11000
11000
|
async function updateAgentFields(client, agentId, change) {
|
|
11001
|
-
const
|
|
11002
|
-
for (const [k, val] of Object.entries(change)) if (val !== void 0)
|
|
11003
|
-
if (Object.keys(
|
|
11001
|
+
const clean6 = {};
|
|
11002
|
+
for (const [k, val] of Object.entries(change)) if (val !== void 0) clean6[k] = val;
|
|
11003
|
+
if (Object.keys(clean6).length === 0) throw new Error("Nothing to update \u2014 pass at least one field to change.");
|
|
11004
11004
|
const before = await readAgent(client, agentId);
|
|
11005
|
-
const body = { ...promptTrio(before), ...
|
|
11005
|
+
const body = { ...promptTrio(before), ...clean6 };
|
|
11006
11006
|
const merged = promptTrio(body);
|
|
11007
11007
|
if (!merged.personality || !merged.goal || !merged.instructions) {
|
|
11008
11008
|
throw new Error(`Agent ${agentId} read back without its personality/goal/instructions, and GoHighLevel refuses an update that does not carry all three. Pass all three explicitly (get_conversation_agent shows what is stored).`);
|
|
11009
11009
|
}
|
|
11010
11010
|
await client.put(`/conversation-ai/agents/${agentId}`, { version: AI_API_VERSION, body });
|
|
11011
11011
|
const after = await readAgent(client, agentId);
|
|
11012
|
-
return { before, changed: Object.keys(
|
|
11012
|
+
return { before, changed: Object.keys(clean6), after };
|
|
11013
11013
|
}
|
|
11014
11014
|
function registerConversationAiTools(server2, client) {
|
|
11015
11015
|
const v = { version: AI_API_VERSION };
|
|
@@ -21145,12 +21145,12 @@ function deriveBrandDna(raw, opts = {}) {
|
|
|
21145
21145
|
fixed = true;
|
|
21146
21146
|
}
|
|
21147
21147
|
const vb = svg.match(/viewBox=["']\s*[\d.-]+\s+[\d.-]+\s+([\d.]+)\s+([\d.]+)/i);
|
|
21148
|
-
const
|
|
21149
|
-
if (
|
|
21150
|
-
logoSvg =
|
|
21151
|
-
logo = { inlineSvg:
|
|
21148
|
+
const clean6 = sanitizeSvgOrDrop(svg);
|
|
21149
|
+
if (clean6) {
|
|
21150
|
+
logoSvg = clean6;
|
|
21151
|
+
logo = { inlineSvg: clean6, whiteFillFixed: fixed, source: bl.where, rights: opts.rights ? "declared" : "inherits", ...vb && { width: parseFloat(vb[1]), height: parseFloat(vb[2]) } };
|
|
21152
21152
|
declared++;
|
|
21153
|
-
evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${
|
|
21153
|
+
evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${clean6 !== svg ? "; scripts, handlers and outside references stripped" : ""}`;
|
|
21154
21154
|
} else {
|
|
21155
21155
|
evidence["logo"] = `${bl.where}: not a plain drawing (or over 60 KB), so it was not kept`;
|
|
21156
21156
|
}
|
|
@@ -32397,7 +32397,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32397
32397
|
}
|
|
32398
32398
|
const specs = wiringSpecs({ testTag: args.testTag, leadTag });
|
|
32399
32399
|
const held2 = [];
|
|
32400
|
-
const
|
|
32400
|
+
const clean6 = (e) => scrub(e instanceof Error ? e.message : String(e), held2);
|
|
32401
32401
|
const survey = async () => {
|
|
32402
32402
|
const refuse4 = (error) => ({ refusal: { ok: false, wrote: "nothing", error } });
|
|
32403
32403
|
const meta = deps.meta();
|
|
@@ -32406,7 +32406,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32406
32406
|
try {
|
|
32407
32407
|
list4 = await writer.listWorkflows();
|
|
32408
32408
|
} catch (e) {
|
|
32409
|
-
return refuse4(`The full list of this account's workflows could not be read from GoHighLevel, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${
|
|
32409
|
+
return refuse4(`The full list of this account's workflows could not be read from GoHighLevel, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${clean6(e)} Run brain_wire_ghl again.`);
|
|
32410
32410
|
}
|
|
32411
32411
|
const found = [];
|
|
32412
32412
|
for (const { id: id2 } of list4) {
|
|
@@ -32414,7 +32414,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32414
32414
|
try {
|
|
32415
32415
|
read2 = await writer.getWorkflow(id2);
|
|
32416
32416
|
} catch (e) {
|
|
32417
|
-
return refuse4(`Workflow ${id2} could not be read from GoHighLevel, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${
|
|
32417
|
+
return refuse4(`Workflow ${id2} could not be read from GoHighLevel, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${clean6(e)} Run brain_wire_ghl again.`);
|
|
32418
32418
|
}
|
|
32419
32419
|
const shape = readShape(read2, id2);
|
|
32420
32420
|
if (shape.kind === "uncertain") return refuse4(`Workflow ${id2} could not be judged: ${shape.why}, so the brain cannot show it has every one of its workflows in view. Nothing was changed. Run brain_wire_ghl again.`);
|
|
@@ -32438,7 +32438,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32438
32438
|
read2 = await writer.getWorkflow(id2);
|
|
32439
32439
|
} catch (e) {
|
|
32440
32440
|
if (isNotFound(e)) continue;
|
|
32441
|
-
return refuse4(`Workflow ${id2} is saved on this computer but GoHighLevel's list did not show it, and it could not be read, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${
|
|
32441
|
+
return refuse4(`Workflow ${id2} is saved on this computer but GoHighLevel's list did not show it, and it could not be read, so the brain cannot show it has every one of its workflows in view. Nothing was changed: ${clean6(e)} Run brain_wire_ghl again.`);
|
|
32442
32442
|
}
|
|
32443
32443
|
const shape = readShape(read2, id2);
|
|
32444
32444
|
if (shape.kind === "uncertain") return refuse4(`Workflow ${id2} is saved on this computer but could not be judged: ${shape.why}. Nothing was changed. Run brain_wire_ghl again.`);
|
|
@@ -32576,7 +32576,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32576
32576
|
if ((await writer.getWorkflow(id2)).status !== "published") await fenced.publishWorkflow(id2);
|
|
32577
32577
|
} catch (e) {
|
|
32578
32578
|
if (e instanceof LeaseLost) throw e;
|
|
32579
|
-
error =
|
|
32579
|
+
error = clean6(e);
|
|
32580
32580
|
}
|
|
32581
32581
|
if (!id2) {
|
|
32582
32582
|
results.push({ workflow: spec.workflowName, built: false, error });
|
|
@@ -32616,7 +32616,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32616
32616
|
});
|
|
32617
32617
|
} catch (e) {
|
|
32618
32618
|
if (e instanceof LeaseLost) throw e;
|
|
32619
|
-
results.push({ workflow: spec.workflowName, id: id2, refreshed: false, readBack: `could not be read back: ${
|
|
32619
|
+
results.push({ workflow: spec.workflowName, id: id2, refreshed: false, readBack: `could not be read back: ${clean6(e)}. Run brain_wire_ghl with rewire: true again to finish`, ...error ? { error } : {} });
|
|
32620
32620
|
}
|
|
32621
32621
|
}
|
|
32622
32622
|
if (staleTest) {
|
|
@@ -32626,7 +32626,7 @@ async function brainWireGhl(deps, args) {
|
|
|
32626
32626
|
results.push({ workflow: "earlier test-tag workflow", id: staleTest, removed: true });
|
|
32627
32627
|
} catch (e) {
|
|
32628
32628
|
if (e instanceof LeaseLost) throw e;
|
|
32629
|
-
results.push({ workflow: "earlier test-tag workflow", id: staleTest, removed: false, error:
|
|
32629
|
+
results.push({ workflow: "earlier test-tag workflow", id: staleTest, removed: false, error: clean6(e), note: "It carries the brain's current key, so it keeps working. Run brain_wire_ghl with rewire: true again to remove it." });
|
|
32630
32630
|
}
|
|
32631
32631
|
}
|
|
32632
32632
|
for (const r of results) {
|
|
@@ -34598,8 +34598,8 @@ function datePatterns(value) {
|
|
|
34598
34598
|
if (!y || !(mo >= 1 && mo <= 12) || !(d >= 1 && d <= 31)) return [];
|
|
34599
34599
|
const year = `(?:${y}|${y.slice(2)})`;
|
|
34600
34600
|
const n = (x) => `0?${x}`;
|
|
34601
|
-
const
|
|
34602
|
-
const out = [`${y}${
|
|
34601
|
+
const sep7 = "[-/.\\s]";
|
|
34602
|
+
const out = [`${y}${sep7}${n(mo)}${sep7}${n(d)}`, `${n(mo)}${sep7}${n(d)}${sep7}${year}`, `${n(d)}${sep7}${n(mo)}${sep7}${year}`];
|
|
34603
34603
|
const month = `(?:${MONTHS2[mo - 1]}|${MONTHS2[mo - 1].slice(0, 3)})\\.?`;
|
|
34604
34604
|
const day = `0?${d}(?:st|nd|rd|th)?`;
|
|
34605
34605
|
out.push(`${month}\\s+${day},?\\s+${y}`, `${day}\\s+(?:of\\s+)?${month},?\\s+${y}`);
|
|
@@ -36643,8 +36643,8 @@ function sanitizeEngagements(raw) {
|
|
|
36643
36643
|
for (const [id2, rec2] of Object.entries(src).slice(0, MAX_CLIENTS)) {
|
|
36644
36644
|
const key2 = str4(id2, 64);
|
|
36645
36645
|
if (!key2 || FORBIDDEN_KEY.has(key2)) continue;
|
|
36646
|
-
const
|
|
36647
|
-
if (
|
|
36646
|
+
const clean6 = sanitizeEngagement(rec2);
|
|
36647
|
+
if (clean6) clients[key2] = clean6;
|
|
36648
36648
|
}
|
|
36649
36649
|
}
|
|
36650
36650
|
return { v: 1, clients: { ...clients }, updated_at: str4(r.updated_at, 40) };
|
|
@@ -40669,7 +40669,33 @@ function buildRunRecord(input) {
|
|
|
40669
40669
|
};
|
|
40670
40670
|
}
|
|
40671
40671
|
function appendBuildStart(input, base) {
|
|
40672
|
-
return appendJournal({ kind: "build-start", runId: input.runId, locationId: input.locationId, locationName: input.locationName, at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(), planId: input.planId }, base);
|
|
40672
|
+
return appendJournal({ kind: "build-start", runId: input.runId, locationId: input.locationId, locationName: input.locationName, at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(), planId: input.planId, ...input.planHash ? { planHash: input.planHash } : {} }, base);
|
|
40673
|
+
}
|
|
40674
|
+
function createdByOtherPlans(locationId2, plan, base) {
|
|
40675
|
+
const records = readJournal(locationId2, base);
|
|
40676
|
+
const starts = /* @__PURE__ */ new Map();
|
|
40677
|
+
for (const r of records) if (r.kind === "build-start") starts.set(r.runId, r);
|
|
40678
|
+
const removedBy = /* @__PURE__ */ new Map();
|
|
40679
|
+
const removed = (runId) => {
|
|
40680
|
+
let m = removedBy.get(runId);
|
|
40681
|
+
if (!m) {
|
|
40682
|
+
m = alreadyRemovedIds(locationId2, runId, base);
|
|
40683
|
+
removedBy.set(runId, m);
|
|
40684
|
+
}
|
|
40685
|
+
return m;
|
|
40686
|
+
};
|
|
40687
|
+
const other = (runId) => {
|
|
40688
|
+
const st = starts.get(runId);
|
|
40689
|
+
if (!st) return false;
|
|
40690
|
+
return st.planHash ? st.planHash !== plan.planHash : st.planId !== plan.planId;
|
|
40691
|
+
};
|
|
40692
|
+
const out = /* @__PURE__ */ new Map();
|
|
40693
|
+
for (const r of records) {
|
|
40694
|
+
if (r.kind !== "build-object" || !other(r.runId)) continue;
|
|
40695
|
+
const o = r.object;
|
|
40696
|
+
if (o?.action === "created" && o.ghlId && !out.has(o.ghlId) && !removed(r.runId).has(o.ghlId)) out.set(o.ghlId, { type: o.type, name: o.name, runId: r.runId });
|
|
40697
|
+
}
|
|
40698
|
+
return out;
|
|
40673
40699
|
}
|
|
40674
40700
|
function appendBuildIntent(input, base) {
|
|
40675
40701
|
return appendJournal({ kind: "build-intent", runId: input.runId, locationId: input.locationId, at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(), intent: { type: input.type, name: input.name } }, base);
|
|
@@ -41524,7 +41550,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
41524
41550
|
engineAcct = { engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked };
|
|
41525
41551
|
if (!engineCheck.ok) {
|
|
41526
41552
|
onEvent({ kind: "error", line: engineCheck.reason });
|
|
41527
|
-
resolve15({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
|
|
41553
|
+
resolve15({ ok: false, error: engineCheck.reason, engineCheckOk: false, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
|
|
41528
41554
|
return;
|
|
41529
41555
|
}
|
|
41530
41556
|
onEvent({ kind: "status", line: `Starting ${spec.name} for ${locationName}.` });
|
|
@@ -43009,9 +43035,41 @@ var init_handoff_run = __esm({
|
|
|
43009
43035
|
}
|
|
43010
43036
|
});
|
|
43011
43037
|
|
|
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
|
+
|
|
43012
43069
|
// src/member-progress.ts
|
|
43013
|
-
function memberProgress(lines, running, ok) {
|
|
43014
|
-
const
|
|
43070
|
+
function memberProgress(lines, running, ok, stage) {
|
|
43071
|
+
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];
|
|
43015
43073
|
if (lines.some((l) => /writing.*review/i.test(l))) result.push("Command OS is preparing the client review.");
|
|
43016
43074
|
if (lines.some((l) => /plain-English version/i.test(l))) result.push("Command OS is preparing the result for review.");
|
|
43017
43075
|
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, "."));
|
|
@@ -43345,6 +43403,7 @@ var VERIFY_PASSED_COUNTING, readLabels, readLabel, BUILDER_SIGNIN_BODY, BUILDER_
|
|
|
43345
43403
|
var init_member_progress = __esm({
|
|
43346
43404
|
"src/member-progress.ts"() {
|
|
43347
43405
|
"use strict";
|
|
43406
|
+
init_stage_failure();
|
|
43348
43407
|
init_workflow_read_connection();
|
|
43349
43408
|
VERIFY_PASSED_COUNTING = "The check finished. The list of jobs left for you is on the Manual tasks step.";
|
|
43350
43409
|
readLabels = { user: "staff list", staff: "staff list", pipeline: "pipeline list", stage: "pipeline stage list", custom_field: "custom field list", tag: "tag list", custom_value: "custom value list", calendar: "calendar list", form: "form list", funnel: "funnel list", page: "page content", workflow: "workflow list", survey: "survey list", email_template: "email template list", sms_template: "SMS template list" };
|
|
@@ -43429,7 +43488,10 @@ function plainFallback(text3, opts = {}) {
|
|
|
43429
43488
|
if (!/[.!?]$/.test(s)) s += ".";
|
|
43430
43489
|
if (opts.nextStep && !/Settings|then |→/.test(s)) {
|
|
43431
43490
|
const hint = nextStepHint(text3);
|
|
43432
|
-
if (hint
|
|
43491
|
+
if (hint && !opts.given?.has(hint)) {
|
|
43492
|
+
s += " " + hint;
|
|
43493
|
+
opts.given?.add(hint);
|
|
43494
|
+
}
|
|
43433
43495
|
}
|
|
43434
43496
|
return s.replace(/—/g, ".").replace(/Brand DNA/gi, "your brand").replace(/Agency OS/gi, "Command OS").replace(/\bMCP\b/g, "GHL Command");
|
|
43435
43497
|
}
|
|
@@ -43437,7 +43499,10 @@ function plainFallbackOutcome(raw) {
|
|
|
43437
43499
|
if (raw.verification) return renderVerifyReport(raw.verification, plainFallbackOutcome({ summary: raw.summary, issues: raw.issues }));
|
|
43438
43500
|
return {
|
|
43439
43501
|
...raw.summary && { summary: plainFallback(raw.summary) },
|
|
43440
|
-
...raw.issues?.length && { issues:
|
|
43502
|
+
...raw.issues?.length && { issues: (() => {
|
|
43503
|
+
const given = /* @__PURE__ */ new Set();
|
|
43504
|
+
return raw.issues.map((x) => plainFallback(x, { nextStep: true, given }));
|
|
43505
|
+
})() }
|
|
43441
43506
|
};
|
|
43442
43507
|
}
|
|
43443
43508
|
function parseOutcomeRewrite(text3, raw) {
|
|
@@ -43960,7 +44025,7 @@ function deriveQueue(input) {
|
|
|
43960
44025
|
title: title(next, name2),
|
|
43961
44026
|
needs: "you",
|
|
43962
44027
|
action: opensStep ? "open" : "mark",
|
|
43963
|
-
why: handRefusal ? `${handRefusal} Open step
|
|
44028
|
+
why: handRefusal ? `${handRefusal} Open step 12 to see them.` : next === 6 ? `${step.why} Open step 13 to read the list and sign off.` : step.why,
|
|
43964
44029
|
rank: RANK.waitingOnYou
|
|
43965
44030
|
});
|
|
43966
44031
|
continue;
|
|
@@ -44194,7 +44259,7 @@ function beyondGoHighLevel(live, engagements, roster, beyond = { ownCalendarConn
|
|
|
44194
44259
|
}
|
|
44195
44260
|
clients.sort((a, b) => a.client.localeCompare(b.client));
|
|
44196
44261
|
const addressesNotRead = [...live?.clientContactsUnread ?? []].sort();
|
|
44197
|
-
const notReadClause = addressesNotRead.length ? ` Then say "Not checked for mail: ${addressesNotRead.join(", ")}" because those clients' addresses could not be read this
|
|
44262
|
+
const notReadClause = addressesNotRead.length ? ` Then say "Not checked for mail: ${addressesNotRead.join(", ")}" because those clients' addresses could not be read for this brief.` : "";
|
|
44198
44263
|
const gmailDo = (clients.length ? 'If you have a Gmail connector: search the last 7 days for mail from the client addresses and domains listed under `clients`, and ONLY those. List every thread whose LAST message is not from the operator under "Client email waiting on you", newest first, one line each: client, subject, how long it has waited. If none: say "No client email is waiting on you."' : 'No client has an address on file, so there is nothing to search for. Say "Client email: no client addresses on file yet" and do not search by name. A client gets addresses once it has a client record (set_income_source), from its sub-account business record and the record\'s own `emails`.') + notReadClause;
|
|
44199
44264
|
const neverMerge = " Never count these as client appointments and never merge the two lists: a GoHighLevel booking synced to Google is the same meeting seen twice.";
|
|
44200
44265
|
const ask = [
|
|
@@ -44284,13 +44349,13 @@ function dailyBrief(input) {
|
|
|
44284
44349
|
} else if (accountsRead === 0 && (live.loading?.length ?? 0) > 0 && !live.unreachable?.length) {
|
|
44285
44350
|
notChecked.push("Every account is still loading from GoHighLevel, so every section below is your own records and the board only for now. Nothing failed; the brief fills in as each account is read.");
|
|
44286
44351
|
} else if (accountsRead === 0) {
|
|
44287
|
-
notChecked.push("Not one account could be read from GoHighLevel this
|
|
44352
|
+
notChecked.push("Not one account could be read from GoHighLevel for this brief, so every section below is your own records and the board only. That is a connection problem to fix, not a quiet day.");
|
|
44288
44353
|
}
|
|
44289
44354
|
for (const note of live?.notes ?? []) notChecked.push(note);
|
|
44290
44355
|
const yourDayNote = input.ownDay?.yourDayNote ?? live?.yourDayNote;
|
|
44291
44356
|
if (yourDayNote) notChecked.push(yourDayNote);
|
|
44292
44357
|
for (const u of live?.unreachable ?? []) {
|
|
44293
|
-
notChecked.push(`${u.client} could not be read this
|
|
44358
|
+
notChecked.push(`${u.client} could not be read for this brief (${u.why}), so nothing from that account is in this brief.`);
|
|
44294
44359
|
}
|
|
44295
44360
|
const stillLoading = live?.loading ?? [];
|
|
44296
44361
|
if (stillLoading.length) {
|
|
@@ -44355,20 +44420,20 @@ function dailyBrief(input) {
|
|
|
44355
44420
|
headline3 = `Nothing is waiting on you. ${neverStarted.length} account${neverStarted.length === 1 ? " has" : "s have"} never been started.`;
|
|
44356
44421
|
} else if (halfRead.length && !unread.length) {
|
|
44357
44422
|
const what = halfRead.length === 1 ? `${halfRead[0].client}'s ${halfRead[0].what}` : `${halfRead.length} accounts were only read in part`;
|
|
44358
|
-
headline3 = `Nothing needs you in what I could read
|
|
44423
|
+
headline3 = `Nothing needs you in what I could read. But ${what}.`;
|
|
44359
44424
|
} else if (unread.length === 1) {
|
|
44360
|
-
headline3 = `Nothing needs you in what I could read
|
|
44425
|
+
headline3 = `Nothing needs you in what I could read. But ${unread[0].client} could not be read for this brief.`;
|
|
44361
44426
|
} else if (unread.length > 1) {
|
|
44362
|
-
headline3 = `Nothing needs you in what I could read
|
|
44427
|
+
headline3 = `Nothing needs you in what I could read. But ${unread.length} accounts could not be read for this brief.`;
|
|
44363
44428
|
} else if (yourDayNote) {
|
|
44364
|
-
headline3 = "Nothing needs you in what I could read
|
|
44429
|
+
headline3 = "Nothing needs you in what I could read. But your own calendar could not be read for this brief.";
|
|
44365
44430
|
} else if (floorUncheckable.length) {
|
|
44366
44431
|
const c = floorUncheckable[0].client;
|
|
44367
44432
|
headline3 = floorUncheckable.length === 1 ? `Nothing needs you \u2014 but ${c}'s price cannot be checked against any floor, so nothing here can tell you whether they are under it.` : `Nothing needs you \u2014 but ${floorUncheckable.length} clients' prices cannot be checked against any floor, ${c} among them.`;
|
|
44368
44433
|
} else if (renewalUnknown.length) {
|
|
44369
44434
|
headline3 = `Nothing needs you \u2014 but ${renewalUnknown.length} active client${renewalUnknown.length === 1 ? " has" : "s have"} no renewal date on file, so a quiet renewals list does not mean a quiet month.`;
|
|
44370
44435
|
} else {
|
|
44371
|
-
headline3 = "Nothing needs you
|
|
44436
|
+
headline3 = "Nothing needs you right now.";
|
|
44372
44437
|
}
|
|
44373
44438
|
const readFrom = accountsRead > 0 ? { accounts: accountsRead } : void 0;
|
|
44374
44439
|
return { date: localDate3(now), headline: headline3, today: today2, yourDay, waitingOnYou, runningNow, neverStarted, renewals, underFloor, payingButQuiet, newLeads, ...live?.leadsBySource?.length ? { leadsBySource: live.leadsBySource } : {}, notChecked, quiet, ...readFrom ? { readFrom } : {}, beyondGoHighLevel: beyondGoHighLevel(live, engagements, roster, input.beyond) };
|
|
@@ -44945,7 +45010,7 @@ async function readYourDay(now, tz, signal, secrets) {
|
|
|
44945
45010
|
}
|
|
44946
45011
|
return { yourDay: ownDayFromCalendar(result) };
|
|
44947
45012
|
} catch {
|
|
44948
|
-
return { yourDayNote: "Your Google Calendar is connected but could not be read this
|
|
45013
|
+
return { yourDayNote: "Your Google Calendar is connected but could not be read for this brief, so your own day is missing from this brief." };
|
|
44949
45014
|
}
|
|
44950
45015
|
}
|
|
44951
45016
|
function operatorTimezone(profileTz) {
|
|
@@ -45824,7 +45889,7 @@ var require_package = __commonJS({
|
|
|
45824
45889
|
"package.json"(exports2, module2) {
|
|
45825
45890
|
module2.exports = {
|
|
45826
45891
|
name: "@elitedcs/ghl-mcp",
|
|
45827
|
-
version: "3.94.
|
|
45892
|
+
version: "3.94.1",
|
|
45828
45893
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
45829
45894
|
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.",
|
|
45830
45895
|
main: "dist/index.js",
|
|
@@ -47287,7 +47352,7 @@ var init_cockpit_cache = __esm({
|
|
|
47287
47352
|
};
|
|
47288
47353
|
AGE_LINE_JS = `
|
|
47289
47354
|
function ageWords(ms){ms=Math.max(0,Number(ms)||0);const m=Math.floor(ms/60000);if(m<1)return "just now";if(m<60)return m+(m===1?" minute":" minutes")+" ago";const h=Math.floor(m/60);if(h<48)return h+(h===1?" hour":" hours")+" ago";const d=Math.floor(h/24);return d+" days ago"}
|
|
47290
|
-
function ageLine(v,now){if(!v)return "";if(v.cold||!v.loadedAt){
|
|
47355
|
+
function ageLine(v,now){if(!v)return "";if(v.cold||!v.loadedAt){if(!v.refreshing&&v.lastRefresh&&v.lastRefresh.text)return "Could not load from GoHighLevel: "+v.lastRefresh.text;return "${COLD_TEXT}\u2026"}
|
|
47291
47356
|
const t=Date.parse(v.loadedAt);const base="Loaded "+ageWords((now==null?Date.now():now)-t);
|
|
47292
47357
|
if(v.refreshing)return base+" \xB7 refreshing\u2026";
|
|
47293
47358
|
if(v.lastRefresh&&v.lastRefresh.text&&Date.parse(v.lastRefresh.at)>=t)return base+" \xB7 Refresh failed: "+v.lastRefresh.text;
|
|
@@ -47505,8 +47570,8 @@ var init_analyze = __esm({
|
|
|
47505
47570
|
function extractUrls(text3, html2) {
|
|
47506
47571
|
const out = /* @__PURE__ */ new Set();
|
|
47507
47572
|
const add2 = (u) => {
|
|
47508
|
-
const
|
|
47509
|
-
if (/^https?:\/\//i.test(
|
|
47573
|
+
const clean6 = u.replace(/&/g, "&").replace(/[.,;:!?]+$/, "");
|
|
47574
|
+
if (/^https?:\/\//i.test(clean6)) out.add(clean6);
|
|
47510
47575
|
};
|
|
47511
47576
|
if (html2) for (const m of html2.matchAll(HREF_RE)) add2(m[1]);
|
|
47512
47577
|
for (const m of text3.matchAll(URL_RE)) add2(m[0]);
|
|
@@ -47702,7 +47767,8 @@ function judgeSteps(rawRows, contactId, workflowNames, lead) {
|
|
|
47702
47767
|
else if (outcome === "unclear") detail = `GoHighLevel logged "${status || "no status"}" for this step, which this version does not know how to read. Check it in GoHighLevel's Execution logs.`;
|
|
47703
47768
|
else if (outcome === "ran") detail = type === "wait_finished" ? "The wait ended." : reason3 && !/^\{/.test(reason3) ? reason3 : "Done.";
|
|
47704
47769
|
else detail = contactDataSkip(type, outcome, reason3, lead) ?? staffGapSkip({ workflowId: wfId, stepId: str9(r.stepId), outcome, reason: reason3 }, lead)?.detail ?? translate(type, outcome, reason3, retryAt).detail;
|
|
47705
|
-
|
|
47770
|
+
const notTested = outcome === "did_nothing" && contactDataSkip(type, outcome, reason3, lead) !== null;
|
|
47771
|
+
rows2.push({ ...base, detail, ...notTested ? { notTested: true } : {}, retryAt: outcome === "failed" && !gaveUp ? retryAt : void 0, ...outcome === "waiting" && retryAt ? { waitUntil: retryAt } : {}, reason: reason3 || void 0, ...gaveUp ? { gaveUp: true } : {} });
|
|
47706
47772
|
}
|
|
47707
47773
|
const sameStep = (a, b) => a.workflowId === b.workflowId && a.pass === b.pass && (a.stepId ?? a.stepName) === (b.stepId ?? b.stepName);
|
|
47708
47774
|
const problems = [];
|
|
@@ -48212,6 +48278,59 @@ async function postJson(url, body) {
|
|
|
48212
48278
|
const r = await guardedPost(url, JSON.stringify(body), { "Content-Type": "application/json", Accept: "application/json" });
|
|
48213
48279
|
return r.status;
|
|
48214
48280
|
}
|
|
48281
|
+
async function removeLeadCards(client, run, save) {
|
|
48282
|
+
if (!run.contactId) return;
|
|
48283
|
+
let rows2;
|
|
48284
|
+
try {
|
|
48285
|
+
rows2 = asArr4(asObj8(await client.get("/opportunities/search", { params: { location_id: run.locationId, contact_id: run.contactId, limit: 100 } })).opportunities).map(asObj8);
|
|
48286
|
+
} catch (e) {
|
|
48287
|
+
run.leadCards = [{ id: "", name: "", removed: false, error: `The test contact's pipeline cards could not be read (${e instanceof Error ? e.message : String(e)}), so any card a workflow made for it is still there.` }];
|
|
48288
|
+
return;
|
|
48289
|
+
}
|
|
48290
|
+
let bornHere = false, contactUnread = false;
|
|
48291
|
+
try {
|
|
48292
|
+
const c = asObj8(asObj8(await client.get(`/contacts/${run.contactId}`)).contact);
|
|
48293
|
+
const added = Date.parse(str10(c.dateAdded) ?? str10(c.createdAt) ?? "");
|
|
48294
|
+
bornHere = (str10(c.email) ?? "").trim().toLowerCase() === run.leadEmail.trim().toLowerCase() && Number.isFinite(added) && added >= run.startedAt - 6e4;
|
|
48295
|
+
} catch {
|
|
48296
|
+
bornHere = false;
|
|
48297
|
+
contactUnread = true;
|
|
48298
|
+
}
|
|
48299
|
+
const ours = rows2.filter((o) => {
|
|
48300
|
+
const contact = str10(o.contactId) ?? str10(asObj8(o.contact).id);
|
|
48301
|
+
const made = Date.parse(str10(o.createdAt) ?? str10(o.dateAdded) ?? "");
|
|
48302
|
+
return contact === run.contactId && Number.isFinite(made) && made >= run.startedAt - 6e4 && Boolean(str10(o.id));
|
|
48303
|
+
});
|
|
48304
|
+
if (!ours.length) return;
|
|
48305
|
+
run.leadCards = ours.map((o) => ({ id: str10(o.id), name: str10(o.name) ?? "Test card", ...str10(o.pipelineId) ? { pipelineId: str10(o.pipelineId) } : {}, removed: false, ...bornHere ? {} : { keptBecauseExisting: true, ...contactUnread ? { contactUnread: true } : {} } }));
|
|
48306
|
+
save(run);
|
|
48307
|
+
if (!bornHere) return;
|
|
48308
|
+
for (const c of run.leadCards) {
|
|
48309
|
+
try {
|
|
48310
|
+
await client.delete(`/opportunities/${c.id}`);
|
|
48311
|
+
c.removed = true;
|
|
48312
|
+
} catch (e) {
|
|
48313
|
+
if (notFound(e)) {
|
|
48314
|
+
c.removed = true;
|
|
48315
|
+
continue;
|
|
48316
|
+
}
|
|
48317
|
+
c.error = e instanceof Error ? e.message : String(e);
|
|
48318
|
+
}
|
|
48319
|
+
}
|
|
48320
|
+
}
|
|
48321
|
+
function leadCardsText(cards) {
|
|
48322
|
+
if (!cards?.length) return "";
|
|
48323
|
+
if (cards.length === 1 && !cards[0].id) return cards[0].error ?? "";
|
|
48324
|
+
const kept = cards.filter((c) => c.keptBecauseExisting && !c.contactUnread), unconfirmed = cards.filter((c) => c.keptBecauseExisting && c.contactUnread);
|
|
48325
|
+
const gone = cards.filter((c) => c.removed), left = cards.filter((c) => !c.removed && !c.keptBecauseExisting);
|
|
48326
|
+
const names = (xs) => xs.map((c) => `"${c.name}"`).join(", ");
|
|
48327
|
+
return [
|
|
48328
|
+
gone.length ? `A workflow made ${gone.length === 1 ? "a pipeline card" : `${gone.length} pipeline cards`} for the test contact (${names(gone)}); the check took ${gone.length === 1 ? "it" : "them"} out of the pipeline again.` : "",
|
|
48329
|
+
kept.length ? `This test used an existing contact, so the check left ${kept.length === 1 ? "this pipeline card" : "these pipeline cards"} for you to check: ${names(kept)}.` : "",
|
|
48330
|
+
unconfirmed.length ? `The check could not confirm this test contact was its own, so it left ${unconfirmed.length === 1 ? "this pipeline card" : "these pipeline cards"} for you to check: ${names(unconfirmed)}.` : "",
|
|
48331
|
+
left.length ? `The check could not take ${left.length === 1 ? "this pipeline card" : "these pipeline cards"} out of the pipeline: ${names(left)}. Clear ${left.length === 1 ? "it" : "them"} by hand in GoHighLevel's pipeline.` : ""
|
|
48332
|
+
].filter(Boolean).join(" ");
|
|
48333
|
+
}
|
|
48215
48334
|
async function listLeadCarriers(client, run) {
|
|
48216
48335
|
const carriers = /* @__PURE__ */ new Map();
|
|
48217
48336
|
let partial = false;
|
|
@@ -48349,6 +48468,9 @@ async function finishShopper(locationId2, runId, deps) {
|
|
|
48349
48468
|
run.status = "done";
|
|
48350
48469
|
run.finishedAt = t;
|
|
48351
48470
|
run.cleanup = await listLeadCarriers(deps.client, run);
|
|
48471
|
+
await removeLeadCards(deps.client, run, (r) => saveRun(r, deps.base));
|
|
48472
|
+
const cardLine = leadCardsText(run.leadCards);
|
|
48473
|
+
if (cardLine) run.cleanup = { ...run.cleanup, detail: `${run.cleanup.detail} ${cardLine}`.trim() };
|
|
48352
48474
|
if (run.fastForward && deps.ffAdmin) {
|
|
48353
48475
|
const { removeCopies: removeCopies2 } = await Promise.resolve().then(() => (init_fastforward(), fastforward_exports));
|
|
48354
48476
|
const problems = await removeCopies2(deps.ffAdmin, run.locationId, run.fastForward.copies.map((c) => ({ copyId: c.copyId, name: c.copyName })), deps.base, now);
|
|
@@ -48831,7 +48953,7 @@ function ringHtml(score2, caption, complete = true) {
|
|
|
48831
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>`;
|
|
48832
48954
|
}
|
|
48833
48955
|
function stepRow(r) {
|
|
48834
|
-
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : OUTCOME[r.outcome] ?? ["", ""];
|
|
48956
|
+
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : r.notTested ? ["", "Not tested"] : OUTCOME[r.outcome] ?? ["", ""];
|
|
48835
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>`;
|
|
48836
48958
|
}
|
|
48837
48959
|
function stepsHtml(a) {
|
|
@@ -49072,7 +49194,11 @@ ${COCKPIT_TYPE_JS}
|
|
|
49072
49194
|
function showStartPanel(ids){
|
|
49073
49195
|
var names=ids.map(function(id){var w=(shownWorkflows||[]).filter(function(x){return x.id===id})[0];return w?w.name:id});
|
|
49074
49196
|
var ff=document.getElementById("ff").checked, email=document.getElementById("owner-email").value, phone=document.getElementById("test-phone").value;
|
|
49075
|
-
|
|
49197
|
+
/* OS audit F18: the run writes a new address made from this one (plusAddress in run.ts), never the typed one; say that. */
|
|
49198
|
+
var em=String(email||"").trim().toLowerCase(),at2=em.lastIndexOf("@"),runEmail=at2>0?em.slice(0,at2).split("+")[0]+"+shopper-(a new code)"+em.slice(at2):em;
|
|
49199
|
+
var li=['A test contact named "Secret Shopper" with an address made from your email, '+esc(runEmail)+' (a new code for each check, still your inbox)'+(phone?' and the phone '+esc(phone):'')+', and the tag command-os-secret-shopper. It stays in the account afterwards; delete it when you no longer need it.'];
|
|
49200
|
+
/* OS audit F19: a copy's "Create opportunity" step makes a real card in the account's real pipeline, and it stays. */
|
|
49201
|
+
li.push('If a workflow makes a pipeline card for the test contact, that card is real and sits in your pipeline while the check runs; the check takes it out again when it ends, and says so if it cannot.');
|
|
49076
49202
|
if(ff)li.push('Temporary copies of '+names.length+' workflow'+(names.length===1?'':'s')+', switched on while the check runs: '+names.map(function(n){return esc("${FF_PREFIX} "+n)}).join(", ")+' (each name also carries a short code). The test lead goes into the copies, not the originals, and the copies are deleted when the check ends.');
|
|
49077
49203
|
else li.push('The test lead goes into '+names.map(esc).join(", ")+'. Those workflows really text and email you, and may alert your staff once.');
|
|
49078
49204
|
// Walk W38: in the copies, staff alerts, tasks and assignments go to one person, marked TEST; say who, by name.
|
|
@@ -49555,7 +49681,7 @@ function deductions(items, capped2) {
|
|
|
49555
49681
|
}
|
|
49556
49682
|
function workflowFlow(steps) {
|
|
49557
49683
|
return `<div class="cds-flow">${steps.map((r) => {
|
|
49558
|
-
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : OUTCOME2[r.outcome] ?? ["", "Unknown"];
|
|
49684
|
+
const [tone, word] = r.gaveUp ? ["bad", "Failed, gave up"] : r.notTested ? ["", "Not tested"] : OUTCOME2[r.outcome] ?? ["", "Unknown"];
|
|
49559
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>`;
|
|
49560
49686
|
}).join("")}</div>`;
|
|
49561
49687
|
}
|
|
@@ -51191,7 +51317,8 @@ function draw(){
|
|
|
51191
51317
|
const sum=money?.summary;
|
|
51192
51318
|
if(sum){tile(cashAll(sum.mrr),"monthly recurring revenue");tile(cashAll(sum.received30.totals),"received, 30 days")}
|
|
51193
51319
|
else if(money){tile(amount(money.owed),"owed on invoices");tile(money.renewals.count,"renewals, 30 days")}
|
|
51194
|
-
|
|
51320
|
+
/* OS audit F3: "Not read" was said when Stripe was simply not connected or held no record. Say which. */
|
|
51321
|
+
else{const w=stripe?panelWord(stripe):"Not connected";tile(w,"monthly recurring revenue");tile(w,"received, 30 days")}
|
|
51195
51322
|
tiles.appendChild(el("p",readLine(),"cds-src stats-meta"));
|
|
51196
51323
|
if(data.accounts.loading>0){const nte=el("p","","cds-callout info notice");nte.appendChild(el("b",plural(data.accounts.loading,"account is","accounts are")+" still loading from GoHighLevel."));nte.appendChild(document.createTextNode(" Nothing failed; the brief fills in as each one is read."));tiles.appendChild(nte)}
|
|
51197
51324
|
if(data.accounts.unread>0){const nte=el("p","","cds-callout warn notice");nte.appendChild(el("b",plural(data.accounts.unread,"account","accounts")+" could not be read."));nte.appendChild(document.createTextNode(" This brief is not the full picture; the unread accounts are named in the notes below."));tiles.appendChild(nte)}
|
|
@@ -54290,7 +54417,7 @@ function parsePricePoints(text3) {
|
|
|
54290
54417
|
const out = [];
|
|
54291
54418
|
const segments2 = text3.split(/\r?\n|;|\.\s+/);
|
|
54292
54419
|
const ENTRY = /^(.*?)\s*[-–—:|]?\s*\$?\s*([\d,]+(?:\.\d+)?)\s*(?:[-–—]\s*\$?\s*([\d,]+(?:\.\d+)?))?\s*(\/\s*[a-z]+|(?:per|an?|each)\s+[a-z]+)?\s*$/i;
|
|
54293
|
-
const
|
|
54420
|
+
const clean6 = (s) => s.replace(/[\s.,:;|–—-]+$/, "").trim();
|
|
54294
54421
|
const num4 = (s) => s.replace(/,/g, "");
|
|
54295
54422
|
const recur = (s) => s ? "/" + s.replace(/^\/|^(?:per|an?|each)\b/i, "").trim().toLowerCase() : "";
|
|
54296
54423
|
for (const segment of segments2) {
|
|
@@ -54299,11 +54426,11 @@ function parsePricePoints(text3) {
|
|
|
54299
54426
|
const m = t.match(ENTRY);
|
|
54300
54427
|
const withTerms = m ? null : t.match(/^(.*?)\s*[-–—:|]?\s*\$\s*([\d,]+(?:\.\d+)?)\s*(.+)$/);
|
|
54301
54428
|
if (m && m[2]) {
|
|
54302
|
-
const name2 =
|
|
54429
|
+
const name2 = clean6(m[1]);
|
|
54303
54430
|
const price = (m[3] ? `${num4(m[2])}-${num4(m[3])}` : num4(m[2])) + recur(m[4]);
|
|
54304
54431
|
out.push({ name: name2 || t, price });
|
|
54305
|
-
} else if (withTerms &&
|
|
54306
|
-
out.push({ name:
|
|
54432
|
+
} else if (withTerms && clean6(withTerms[1])) {
|
|
54433
|
+
out.push({ name: clean6(withTerms[1]), price: num4(withTerms[2]) + (withTerms[3].trim().startsWith("/") ? "" : " ") + withTerms[3].trim() });
|
|
54307
54434
|
} else {
|
|
54308
54435
|
out.push({ name: t, price: "" });
|
|
54309
54436
|
}
|
|
@@ -55228,17 +55355,17 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
|
|
|
55228
55355
|
function staffGateReason(check, stageName) {
|
|
55229
55356
|
if (check.status === "ready") return null;
|
|
55230
55357
|
if (check.status === "unreadable") {
|
|
55231
|
-
return `${stageName} is waiting: we could not read this client's staff in GoHighLevel. Click Check again in step
|
|
55358
|
+
return `${stageName} is waiting: we could not read this client's staff in GoHighLevel. Click Check again in step 8, Staff.`;
|
|
55232
55359
|
}
|
|
55233
55360
|
if (check.status === "needs_list") {
|
|
55234
55361
|
return `${stageName} is waiting: nobody is on the team yet. Add them under "Your team" in step 5, Intake.`;
|
|
55235
55362
|
}
|
|
55236
|
-
return `${stageName} is waiting: ${listSentence(check.missing.map(personLabel))} ${check.missing.length === 1 ? "is" : "are"} not in this client's GoHighLevel account yet. Add ${check.missing.length === 1 ? "them" : "everyone"} in step
|
|
55363
|
+
return `${stageName} is waiting: ${listSentence(check.missing.map(personLabel))} ${check.missing.length === 1 ? "is" : "are"} not in this client's GoHighLevel account yet. Add ${check.missing.length === 1 ? "them" : "everyone"} in step 8, Staff. Nothing is built with a blank name.`;
|
|
55237
55364
|
}
|
|
55238
55365
|
function staffPreflightLine(check) {
|
|
55239
|
-
if (!check) return { key: "staff", ok: false, text: "We could not check this client's staff. Use Check again in step
|
|
55366
|
+
if (!check) return { key: "staff", ok: false, text: "We could not check this client's staff. Use Check again in step 8, Staff." };
|
|
55240
55367
|
if (check.status === "ready") return { key: "staff", ok: true, text: `Staff in this account: ${listSentence(check.matched.map((m) => m.name))}.` };
|
|
55241
|
-
return { key: "staff", ok: false, text: `${check.headline}. Finish step
|
|
55368
|
+
return { key: "staff", ok: false, text: `${check.headline}. Finish step 8, Staff, before Build.` };
|
|
55242
55369
|
}
|
|
55243
55370
|
async function readStaffCheck(deps, listed, now) {
|
|
55244
55371
|
let users;
|
|
@@ -55386,8 +55513,8 @@ var init_staff_first = __esm({
|
|
|
55386
55513
|
STAFF_STEP_ABOUT = "Calendars and lead alerts need real people. List everyone who takes appointments and everyone who should hear about new leads. We check this client's account for each email address before anything is built, so no calendar or alert is built with a blank name to fix later.";
|
|
55387
55514
|
STAFF_LIST_HELP = "One person per line: name, email, role. For example: Jane Smith, jane@yourclinic.com, Front desk. If it is just you, add yourself.";
|
|
55388
55515
|
STAFF_HOW_TO_ADD = `In this client's GoHighLevel account, go to ${GHL_ADD_STAFF_CLICK_PATH}. For each person above, ${GHL_ADD_STAFF_FORM_NOTE} shown here. When you are done, click Check again and we will read the account and confirm each person.`;
|
|
55389
|
-
STAFF_REVIEW_WARNING = "Your staff are not all in this client's account yet. Build will not start until they are: without them the calendars cannot be built, and the workflows that start from a booking would be left as drafts with no calendar chosen. Finish step
|
|
55390
|
-
STAFF_REVIEW_UNCHECKED = "We have not checked this client's staff yet. Build will not start until everyone who takes appointments is in the account. Step
|
|
55516
|
+
STAFF_REVIEW_WARNING = "Your staff are not all in this client's account yet. Build will not start until they are: without them the calendars cannot be built, and the workflows that start from a booking would be left as drafts with no calendar chosen. Finish step 8, Staff, first.";
|
|
55517
|
+
STAFF_REVIEW_UNCHECKED = "We have not checked this client's staff yet. Build will not start until everyone who takes appointments is in the account. Step 8, Staff, checks them.";
|
|
55391
55518
|
STAFF_GATED_STAGES = /* @__PURE__ */ new Set([3]);
|
|
55392
55519
|
fullName = (c) => [c.firstName, c.lastName === "-" ? "" : c.lastName].filter(Boolean).join(" ");
|
|
55393
55520
|
STAFF_ADMIN_MARK = "Admin login";
|
|
@@ -55500,7 +55627,7 @@ function intakeAnswer2(intake, key2) {
|
|
|
55500
55627
|
const label2 = questionByKey(key2)?.label;
|
|
55501
55628
|
const direct = intake[key2] ?? (label2 ? intake[label2] : void 0);
|
|
55502
55629
|
const hit = direct ?? Object.entries(intake).find(([k]) => label2 && k.trim().toLowerCase() === label2.trim().toLowerCase())?.[1];
|
|
55503
|
-
return
|
|
55630
|
+
return clean4(Array.isArray(hit) ? hit.join(", ") : hit);
|
|
55504
55631
|
}
|
|
55505
55632
|
function readBackBroken(line4) {
|
|
55506
55633
|
if (line4.state === "CHECKED") return false;
|
|
@@ -55511,7 +55638,7 @@ function readBackBroken(line4) {
|
|
|
55511
55638
|
function withoutBrokenWorkflows(plan, lines) {
|
|
55512
55639
|
if (!plan) return null;
|
|
55513
55640
|
if (!lines?.length || !plan.workflows?.length) return plan;
|
|
55514
|
-
return { ...plan, workflows: plan.workflows.filter((w) => !lines.some((l) =>
|
|
55641
|
+
return { ...plan, workflows: plan.workflows.filter((w) => !lines.some((l) => clean4(l.name) === clean4(w.name) && readBackBroken(l))) };
|
|
55515
55642
|
}
|
|
55516
55643
|
function builtFeatures(plan) {
|
|
55517
55644
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -55528,9 +55655,9 @@ function hasWorkflow(plan, ref) {
|
|
|
55528
55655
|
return (plan?.workflows ?? []).some((w) => w.ref === ref);
|
|
55529
55656
|
}
|
|
55530
55657
|
function workflowsConfirmed(plan, lines) {
|
|
55531
|
-
const names = (plan?.workflows ?? []).map((w) =>
|
|
55658
|
+
const names = (plan?.workflows ?? []).map((w) => clean4(w.name)).filter(Boolean);
|
|
55532
55659
|
if (!names.length || !lines?.length) return false;
|
|
55533
|
-
return names.every((n) => lines.some((l) =>
|
|
55660
|
+
return names.every((n) => lines.some((l) => clean4(l.name) === n && l.state === "CHECKED"));
|
|
55534
55661
|
}
|
|
55535
55662
|
function durationWords(c) {
|
|
55536
55663
|
if (!c.slotDuration) return "";
|
|
@@ -55554,8 +55681,8 @@ function isProgramFollowUp(plan) {
|
|
|
55554
55681
|
function context(src) {
|
|
55555
55682
|
const plan = src.plan ?? null;
|
|
55556
55683
|
const features = builtFeatures(plan);
|
|
55557
|
-
const calendars = (plan?.calendars ?? []).filter((c) =>
|
|
55558
|
-
name: calendarLabel(
|
|
55684
|
+
const calendars = (plan?.calendars ?? []).filter((c) => clean4(c.name)).map((c) => ({
|
|
55685
|
+
name: calendarLabel(clean4(c.name)),
|
|
55559
55686
|
duration: durationWords(c),
|
|
55560
55687
|
team: (c.teamMemberRefs ?? []).map(String)
|
|
55561
55688
|
}));
|
|
@@ -55564,8 +55691,8 @@ function context(src) {
|
|
|
55564
55691
|
const items = boughtItemsFor(offered.length ? offered : features);
|
|
55565
55692
|
const { ticks, source } = src.boughtState && src.recordKey ? ticksFor(src.boughtState, src.recordKey) : { ticks: [], source: "all" };
|
|
55566
55693
|
const confirmedTicks = source !== "all";
|
|
55567
|
-
const notify2 =
|
|
55568
|
-
const calls =
|
|
55694
|
+
const notify2 = clean4(src.roster?.notifyName) || intakeAnswer2(src.intake, "notify_name") || void 0;
|
|
55695
|
+
const calls = clean4(src.roster?.callsName) || intakeAnswer2(src.intake, "calls_name") || void 0;
|
|
55569
55696
|
const notifyShown = notify2 ? teamNames(src).find((n) => sameName(n, notify2)) : void 0;
|
|
55570
55697
|
const callAnswering = src.recordKey && src.boughtState?.asks?.[src.recordKey]?.callAnswering || [];
|
|
55571
55698
|
return { features, live: workflowsConfirmed(plan, src.lines), calendars, notify: notify2, ...notifyShown && { notifyShown }, calls, items, ticked: confirmedTicks ? ticks : [], callAnswering, confirmedTicks, programFollowUp: isProgramFollowUp(plan), review: reviewTimingOf(plan), noShowRescue: hasWorkflow(plan, "workflow.no_show"), reply: replyChannels(plan) };
|
|
@@ -55578,12 +55705,12 @@ function calendarPhrase(ctx) {
|
|
|
55578
55705
|
return names.length ? `the ${listWords(names, "or")} calendar` : "your booking calendar";
|
|
55579
55706
|
}
|
|
55580
55707
|
function teamNames(src) {
|
|
55581
|
-
const fromPlan = (src.plan?.users ?? []).map((u) =>
|
|
55708
|
+
const fromPlan = (src.plan?.users ?? []).map((u) => clean4(`${u.firstName ?? ""} ${u.lastName ?? ""}`)).filter(Boolean);
|
|
55582
55709
|
if (fromPlan.length) return fromPlan;
|
|
55583
|
-
return parseStaffText(src.roster?.lines || intakeAnswer2(src.intake, "staff_members")).people.map((p) =>
|
|
55710
|
+
return parseStaffText(src.roster?.lines || intakeAnswer2(src.intake, "staff_members")).people.map((p) => clean4(p.name)).filter(Boolean);
|
|
55584
55711
|
}
|
|
55585
55712
|
function calendarLabel(name2) {
|
|
55586
|
-
const n =
|
|
55713
|
+
const n = clean4(name2);
|
|
55587
55714
|
const prose = !n || n.length > 40 || n.split(" ").length > 6 || /[@$%;:()\[\],]|\d{3}[-. )]*\d{3,}|https?:|www\./i.test(n);
|
|
55588
55715
|
return prose ? "booking calendar" : n;
|
|
55589
55716
|
}
|
|
@@ -55707,10 +55834,10 @@ function stepsOf(ctx, plan) {
|
|
|
55707
55834
|
}
|
|
55708
55835
|
function teamOf(ctx, src) {
|
|
55709
55836
|
const plan = src.plan;
|
|
55710
|
-
const people = (plan?.users ?? []).map((u) => ({ name:
|
|
55837
|
+
const people = (plan?.users ?? []).map((u) => ({ name: clean4(`${u.firstName ?? ""} ${u.lastName ?? ""}`), ref: u.ref })).filter((p) => p.name);
|
|
55711
55838
|
if (!people.length) {
|
|
55712
55839
|
const listed = parseStaffText(src.roster?.lines || intakeAnswer2(src.intake, "staff_members")).people;
|
|
55713
|
-
for (const p of listed) people.push({ name:
|
|
55840
|
+
for (const p of listed) people.push({ name: clean4(p.name) });
|
|
55714
55841
|
}
|
|
55715
55842
|
const rows2 = [];
|
|
55716
55843
|
for (const p of people) {
|
|
@@ -55737,7 +55864,7 @@ function teamOf(ctx, src) {
|
|
|
55737
55864
|
}
|
|
55738
55865
|
function stagesOf2(plan) {
|
|
55739
55866
|
const first2 = (plan?.pipelines ?? [])[0];
|
|
55740
|
-
const all = [...first2?.stages ?? []].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((s) =>
|
|
55867
|
+
const all = [...first2?.stages ?? []].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((s) => clean4(s.name)).filter(Boolean);
|
|
55741
55868
|
return all.length > MAX_STAGE_CHIPS ? [...all.slice(0, MAX_STAGE_CHIPS), `+${all.length - MAX_STAGE_CHIPS} more`] : all;
|
|
55742
55869
|
}
|
|
55743
55870
|
function boughtOf(ctx) {
|
|
@@ -55790,12 +55917,12 @@ function buildOwnerHandoff(input) {
|
|
|
55790
55917
|
const ctx = context(src);
|
|
55791
55918
|
const { rows: rows2, source } = problemsOf(ctx, src);
|
|
55792
55919
|
const { steps, after } = stepsOf(ctx, src.plan);
|
|
55793
|
-
const agencyName =
|
|
55920
|
+
const agencyName = clean4(src.agency?.name) || void 0;
|
|
55794
55921
|
const timeZone = clientTimeZone(src);
|
|
55795
55922
|
return {
|
|
55796
55923
|
// The business's own name from the intake, else the GoHighLevel account's (audit 2026-10-28 #25: an account named
|
|
55797
55924
|
// for the agency's test titled the client's page).
|
|
55798
|
-
accountName: intakeAnswer2(src.intake, "business_name") ||
|
|
55925
|
+
accountName: intakeAnswer2(src.intake, "business_name") || clean4(src.accountName),
|
|
55799
55926
|
checkedAt: src.checkedAt,
|
|
55800
55927
|
...timeZone && { timeZone },
|
|
55801
55928
|
...src.revision !== void 0 && { revision: src.revision },
|
|
@@ -55804,7 +55931,7 @@ function buildOwnerHandoff(input) {
|
|
|
55804
55931
|
...agencyName && { name: agencyName },
|
|
55805
55932
|
accent: isHex(src.agency?.accent) ? src.agency.accent : DEFAULT_ACCENT,
|
|
55806
55933
|
...isRasterDataUri(src.agency?.logoDataUri) && { logoDataUri: src.agency.logoDataUri },
|
|
55807
|
-
...
|
|
55934
|
+
...clean4(src.agency?.contact) && { contact: clean4(src.agency?.contact) }
|
|
55808
55935
|
},
|
|
55809
55936
|
summary: summaryOf2(ctx, src.plan),
|
|
55810
55937
|
problems: rows2,
|
|
@@ -55952,7 +56079,7 @@ ${sections.join("\n")}
|
|
|
55952
56079
|
</script>
|
|
55953
56080
|
</body></html>`;
|
|
55954
56081
|
}
|
|
55955
|
-
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,
|
|
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;
|
|
55956
56083
|
var init_owner_handoff = __esm({
|
|
55957
56084
|
"src/command-os/owner-handoff.ts"() {
|
|
55958
56085
|
"use strict";
|
|
@@ -56029,9 +56156,9 @@ var init_owner_handoff = __esm({
|
|
|
56029
56156
|
UNIT_MINUTES = { minute: 1, minutes: 1, hour: 60, hours: 60, day: 1440, days: 1440, week: 10080, weeks: 10080 };
|
|
56030
56157
|
NUMBER_WORDS = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"];
|
|
56031
56158
|
countWords = (n, unit) => `${NUMBER_WORDS[n] ?? String(n)} ${unit}${n === 1 ? "" : "s"}`;
|
|
56032
|
-
|
|
56159
|
+
clean4 = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
56033
56160
|
cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
56034
|
-
sameName = (a, b) => Boolean(a && b &&
|
|
56161
|
+
sameName = (a, b) => Boolean(a && b && clean4(a).toLowerCase() === clean4(b).toLowerCase());
|
|
56035
56162
|
FINDING_TO_BOUGHT = Object.fromEntries(
|
|
56036
56163
|
ASSESSMENT_FEATURES.filter((f) => "finding" in f).map((f) => [f.finding, f.id])
|
|
56037
56164
|
);
|
|
@@ -56895,6 +57022,8 @@ var init_cockpit_routes = __esm({
|
|
|
56895
57022
|
get("/api/snapshots", "agency", BOTH),
|
|
56896
57023
|
get("/api/connections", "agency", BOTH),
|
|
56897
57024
|
get("/api/design/run", "agency", BOTH),
|
|
57025
|
+
// OS audit F10: the finished design opens from the brand page (served sandboxed: it is the model's page).
|
|
57026
|
+
page("/design/result", "agency", BOTH),
|
|
56898
57027
|
get("/api/shopper/report-seat", "agency", BOTH),
|
|
56899
57028
|
// A report's branding is kept per client (report-brand-choices.json[client]): a one-client row, the client in the body.
|
|
56900
57029
|
via(post("/api/shopper/report-brand", "client", BOTH), { body: "client" }),
|
|
@@ -61333,19 +61462,36 @@ function startDesignRun(request2, opts = {}) {
|
|
|
61333
61462
|
});
|
|
61334
61463
|
return job;
|
|
61335
61464
|
}
|
|
61465
|
+
function memberRunLines(lines) {
|
|
61466
|
+
return lines.filter((l) => PROBLEM2.test(l) || !CHATTER.some((re) => re.test(l))).map((l) => l.replace(ABS_PATH, (p) => p.split(/[\\/]/).filter(Boolean).pop() ?? p));
|
|
61467
|
+
}
|
|
61336
61468
|
function designRunStatus(id2) {
|
|
61337
61469
|
const job = jobs.get(id2);
|
|
61338
61470
|
if (!job) return null;
|
|
61471
|
+
const shown = { ...job, tail: memberRunLines(job.tail) };
|
|
61339
61472
|
const runJson = (0, import_node_path13.join)(job.outDir, "run.json");
|
|
61340
|
-
if (!(0, import_node_fs15.existsSync)(runJson)) return
|
|
61473
|
+
if (!(0, import_node_fs15.existsSync)(runJson)) return shown;
|
|
61341
61474
|
try {
|
|
61342
|
-
return { ...
|
|
61475
|
+
return { ...shown, run: JSON.parse((0, import_node_fs15.readFileSync)(runJson, "utf8")) };
|
|
61343
61476
|
} catch {
|
|
61344
|
-
return
|
|
61477
|
+
return shown;
|
|
61478
|
+
}
|
|
61479
|
+
}
|
|
61480
|
+
function designResultFile(id2) {
|
|
61481
|
+
const job = jobs.get(id2);
|
|
61482
|
+
if (!job || job.status !== "done") return null;
|
|
61483
|
+
try {
|
|
61484
|
+
const run = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path13.join)(job.outDir, "run.json"), "utf8"));
|
|
61485
|
+
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;
|
|
61489
|
+
} catch {
|
|
61490
|
+
return null;
|
|
61345
61491
|
}
|
|
61346
61492
|
}
|
|
61347
61493
|
function listDesignRuns() {
|
|
61348
|
-
return [...jobs.values()].sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
61494
|
+
return [...jobs.values()].map((j) => ({ ...j, tail: memberRunLines(j.tail) })).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
61349
61495
|
}
|
|
61350
61496
|
function cancelDesignRun(id2) {
|
|
61351
61497
|
const child = children.get(id2);
|
|
@@ -61359,7 +61505,7 @@ function cancelDesignRun(id2) {
|
|
|
61359
61505
|
}
|
|
61360
61506
|
return true;
|
|
61361
61507
|
}
|
|
61362
|
-
var import_node_child_process6, import_node_fs15, import_node_path13, import_node_crypto29, SLUG2, MODEL, JOINED_FLAG, jobs, children;
|
|
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;
|
|
61363
61509
|
var init_run_jobs = __esm({
|
|
61364
61510
|
"src/command-os/design/run-jobs.ts"() {
|
|
61365
61511
|
"use strict";
|
|
@@ -61375,6 +61521,9 @@ var init_run_jobs = __esm({
|
|
|
61375
61521
|
JOINED_FLAG = /^--[a-z][a-z-]*=/;
|
|
61376
61522
|
jobs = /* @__PURE__ */ new Map();
|
|
61377
61523
|
children = /* @__PURE__ */ new Map();
|
|
61524
|
+
CHATTER = [/^\s*\[ghl-mcp\] Token registry: loaded key/i, /injected env \(\d+\) from .*\btip:/i, /^\s*[\u25c7\u25c8]\s.*\btip:/i];
|
|
61525
|
+
PROBLEM2 = /\b(warning|failed|fail|could not|cannot|invalid|error|bootstrap|denied|refused|unauthori[sz]ed)\b/i;
|
|
61526
|
+
ABS_PATH = /(?:\/(?:private|Users|var|tmp|home|opt)\/[^\s"')]+|[A-Za-z]:\\[^\s"')]+)/g;
|
|
61378
61527
|
}
|
|
61379
61528
|
});
|
|
61380
61529
|
|
|
@@ -61384,9 +61533,9 @@ var init_run_expectations = __esm({
|
|
|
61384
61533
|
"src/command-os/design/run-expectations.ts"() {
|
|
61385
61534
|
"use strict";
|
|
61386
61535
|
RUN_MAX_PASSES = 3;
|
|
61387
|
-
RUN_PASS_MINUTES = "
|
|
61388
|
-
RUN_PASS_COST = "$
|
|
61389
|
-
RUN_TOTAL_MINUTES = "
|
|
61536
|
+
RUN_PASS_MINUTES = "5 to 13 minutes";
|
|
61537
|
+
RUN_PASS_COST = "$0.85 to $1.75";
|
|
61538
|
+
RUN_TOTAL_MINUTES = "15 to 40 minutes";
|
|
61390
61539
|
RUN_TOTAL_COST = "about $5";
|
|
61391
61540
|
RUN_EXPECTATION = `Each pass takes ${RUN_PASS_MINUTES} and costs ${RUN_PASS_COST} of your own Claude usage, and a run may take up to ${RUN_MAX_PASSES} passes. Allow ${RUN_TOTAL_MINUTES} and up to ${RUN_TOTAL_COST}.`;
|
|
61392
61541
|
}
|
|
@@ -63297,20 +63446,20 @@ function presetOwnWords(s) {
|
|
|
63297
63446
|
return s.replace(INLINE_TOKEN_RE, (_whole, _path, def) => def !== void 0 ? ` ${def.trim()} ` : " ").replace(/\|\|/g, " ");
|
|
63298
63447
|
}
|
|
63299
63448
|
function claimSafeBrief(brief, blocked) {
|
|
63300
|
-
const
|
|
63449
|
+
const clean6 = (v) => {
|
|
63301
63450
|
if (typeof v === "string") return blocked(v).length > 0 ? void 0 : v;
|
|
63302
|
-
if (Array.isArray(v)) return v.map(
|
|
63451
|
+
if (Array.isArray(v)) return v.map(clean6).filter((x) => x !== void 0);
|
|
63303
63452
|
if (v && typeof v === "object") {
|
|
63304
63453
|
const out = {};
|
|
63305
63454
|
for (const [k, x] of Object.entries(v)) {
|
|
63306
|
-
const c =
|
|
63455
|
+
const c = clean6(x);
|
|
63307
63456
|
if (c !== void 0) out[k] = c;
|
|
63308
63457
|
}
|
|
63309
63458
|
return out;
|
|
63310
63459
|
}
|
|
63311
63460
|
return v;
|
|
63312
63461
|
};
|
|
63313
|
-
return
|
|
63462
|
+
return clean6(brief);
|
|
63314
63463
|
}
|
|
63315
63464
|
function isHollowMessage(s) {
|
|
63316
63465
|
return visibleText2(stripOptOutLines(s)).replace(/[\s.,!?-]/g, "") === "";
|
|
@@ -65278,6 +65427,8 @@ function stepProgress(i) {
|
|
|
65278
65427
|
i.assessment === "attached" || i.assessment === "without" || i.assessment === "" && [2, 3, 4, 5, 6].some((n) => i.stages[n] === "done"),
|
|
65279
65428
|
// Old clients' stage 0 was a form install; it counts only once answers are really in (review of 64da59d).
|
|
65280
65429
|
stage(0) && i.intakeIn !== false,
|
|
65430
|
+
// Clients that approved under the old order (bought inside Approve) count as answered.
|
|
65431
|
+
Boolean(i.scopeSaved) || stage(2) && i.approved,
|
|
65281
65432
|
stage(1),
|
|
65282
65433
|
i.staffReady || stage(3),
|
|
65283
65434
|
stage(2) && i.approved,
|
|
@@ -65305,6 +65456,7 @@ var init_build_steps = __esm({
|
|
|
65305
65456
|
"Industry",
|
|
65306
65457
|
"Assessment",
|
|
65307
65458
|
"Intake",
|
|
65459
|
+
"What they bought",
|
|
65308
65460
|
"Brief",
|
|
65309
65461
|
"Staff",
|
|
65310
65462
|
"Approve",
|
|
@@ -65319,9 +65471,10 @@ var init_build_steps = __esm({
|
|
|
65319
65471
|
"What kind of business is this? It picks the intake questions and the plan.",
|
|
65320
65472
|
"Talk with the owner and attach the Assessment, or build without one.",
|
|
65321
65473
|
"The business details and your team. We fill in what your notes and the call already say.",
|
|
65474
|
+
"Tick what your client paid for, and confirm the questions it asks with them. The plan and the review are written for exactly this.",
|
|
65322
65475
|
"Claude reads the intake and writes the plan for this client.",
|
|
65323
65476
|
"Your team from step 5 must be in the client's GoHighLevel account before anything is built for them.",
|
|
65324
|
-
"Read the plan
|
|
65477
|
+
"Read the plan and say yes. Nothing is built until you do.",
|
|
65325
65478
|
"We build the plan in GoHighLevel. You can watch it happen.",
|
|
65326
65479
|
"We check that every part works.",
|
|
65327
65480
|
"The jobs you do by hand in GoHighLevel. Each says how, and gives you a question to ask Claude for step by step help.",
|
|
@@ -65363,7 +65516,7 @@ function bsInputs(c,state,run){const id=c.locationId;const st=(((state.clients||
|
|
|
65363
65516
|
const sc=(state.staffChecks||{})[id];const ii=((state.intakeInputs||{})[id])||{};
|
|
65364
65517
|
const intakeIn=Boolean((state.intakeCompleted||{})[id])||ii.status==="received";
|
|
65365
65518
|
return {intakeIn:intakeIn,typeConfirmed:Boolean(acc.typeConfirmed),parked:["template","hidden"].includes(typeOf(state,id)),industry:acc.industry||"",assessment:assess,stages:st,stale:stale,
|
|
65366
|
-
running:(run&&run.running&&run.locationId===id&&typeof run.stage==="number")?run.stage:-1,staffReady:Boolean(sc&&sc.status==="ready"),approved:Boolean((state.buildApproved||{})[id])}}
|
|
65519
|
+
running:(run&&run.running&&run.locationId===id&&typeof run.stage==="number")?run.stage:-1,staffReady:Boolean(sc&&sc.status==="ready"),approved:Boolean((state.buildApproved||{})[id]),scopeSaved:scopeChosen(state,id)}}
|
|
65367
65520
|
/* Where a client stands, for the My clients list: "Waiting on step 5 of 12: Intake". */
|
|
65368
65521
|
function bsWhere(c,state,run){const t=typeOf(state,c.locationId);if(t==="template"||t==="hidden")return {step:-1,text:"Not built: a "+TYPE_LABELS[t].toLowerCase()+" account.",cta:"Open"};
|
|
65369
65522
|
const p=stepProgress(bsInputs(c,state,run));const n=BS_NAMES.length;
|
|
@@ -65440,14 +65593,15 @@ function bsSummary(n,c,state){const id=c.locationId;const acc=((state.accounts||
|
|
|
65440
65593
|
if(n===2){const f=INDUSTRIES2.find(x=>x.slug===acc.industry);return f?f.label:(acc.industry||"")}
|
|
65441
65594
|
if(n===3){const a=bsInputs(c,state,null).assessment;return a==="attached"?"Assessment attached.":a==="without"?"Building without an Assessment.":"Answered before this page existed."}
|
|
65442
65595
|
if(n===4)return "Intake finished.";
|
|
65443
|
-
if(n===5)
|
|
65444
|
-
if(n===6)
|
|
65445
|
-
if(n===7){const
|
|
65596
|
+
if(n===5){const sp=(state.scope||{})[id]||{};const k=(sp.features||[]).length;return sp.wholePreset?"Everything in the plan.":"Saved: "+k+" "+(k===1?"feature":"features")+" bought."}
|
|
65597
|
+
if(n===6)return "Brief written. The Blueprint Review is ready for step 9.";
|
|
65598
|
+
if(n===7){const sc=(state.staffChecks||{})[id];return sc&&sc.matched&&sc.matched.length?"In the account: "+sc.matched.map(m=>m.name).join(", ")+".":"Staff confirmed."}
|
|
65599
|
+
if(n===8){const a=(state.buildApproved||{})[id];return a?"Approved "+niceDate(a,true)+".":"Approved."}
|
|
65446
65600
|
/* Run 9: the folded step said only "Built.". It says what was made, from the build's own record. */
|
|
65447
|
-
if(n===
|
|
65601
|
+
if(n===9){const bv0=(((state.buildViews||{})[id])||{});const made=(bv0.made&&bv0.made.length)?bv0.made:(bv0.built||[]).filter(x=>x.status==="created");if(!made.length)return "Built.";
|
|
65448
65602
|
const by={};for(const x of made)by[x.type]=(by[x.type]||0)+1;const KIND={pipeline:"pipeline",stage:"pipeline stage",field:"custom field",tag:"tag",cv:"custom value",calendar:"calendar",form:"form",email_template:"email template",sms_template:"text template",workflow:"draft automation",user:"staff login"};
|
|
65449
65603
|
return "Built in GoHighLevel: "+Object.entries(by).map(([k,v])=>v+" "+plural(v,KIND[k]||k)).join(", ")+". Nothing is switched on."}
|
|
65450
|
-
if(n===
|
|
65604
|
+
if(n===10){const oc=(state.outcomes||{})[id+":4"];const must=bsMustDo(c,state).length;
|
|
65451
65605
|
/* Never "clean" while a job that blocks go-live is open (blind run 2: "clean" beside "no texts can be sent"). */
|
|
65452
65606
|
/* Command-center 2026-10-03: never "clean" on ticks alone. */
|
|
65453
65607
|
const tk=(((state.jobCounts||{})[id])||{}).ticked||0;
|
|
@@ -65456,14 +65610,14 @@ function bsSummary(n,c,state){const id=c.locationId;const acc=((state.accounts||
|
|
|
65456
65610
|
const conf=((oc&&oc.verification&&oc.verification.memberJobs)||{}).confirmed;
|
|
65457
65611
|
const lead=typeof conf==="number"?"GoHighLevel confirms "+conf+" "+plural(conf,"item")+".":"The check finished.";
|
|
65458
65612
|
const mine=(tk||typeof conf==="number")?" You confirmed "+tk+" yourself.":"";
|
|
65459
|
-
if(oc&&oc.ok===true)return must?lead+mine+" "+must+" "+(must===1?"job is":"jobs are")+" required before sign-off: see step
|
|
65613
|
+
if(oc&&oc.ok===true)return must?lead+mine+" "+must+" "+(must===1?"job is":"jobs are")+" required before sign-off: see step 12.":lead+mine+" Nothing else is required before sign-off.";
|
|
65460
65614
|
return oc?"Checked. Some parts need another look: see the notes.":"Checked."}
|
|
65461
|
-
if(n===
|
|
65615
|
+
if(n===11)return "Hand jobs confirmed.";
|
|
65462
65616
|
return "Signed off. Your next jobs in GoHighLevel are listed on this step."}
|
|
65463
65617
|
/* Steps 2, 3, 4, 5 and 7 can be changed later; the server marks what comes after them out of date. */
|
|
65464
|
-
const BS_CHANGEABLE=[1,2,3,4];
|
|
65618
|
+
const BS_CHANGEABLE=[1,2,3,4,5];
|
|
65465
65619
|
/* The saved stage each step runs or marks (null: a step the cockpit keeps itself). */
|
|
65466
|
-
const BS_STAGE_OF=[null,null,null,null,0,1,null,2,3,4,5,6];
|
|
65620
|
+
const BS_STAGE_OF=[null,null,null,null,0,null,1,null,2,3,4,5,6];
|
|
65467
65621
|
function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
65468
65622
|
/* An older build recorded in the account is named, never applied (run 7: a fresh start showed old steps as done). */
|
|
65469
65623
|
if(n===0){const old=(state.olderShared||{})[id];if(old){const done=(old.stages||[]).filter(s=>s==="done").length;box.appendChild(mk("p","cds-callout","An older build of this client is recorded in its GoHighLevel account ("+done+" "+plural(done,"step")+" done, last updated "+(niceDate(old.updated_at,true)||old.updated_at)+"). It does not match this client's intake here, so none of it is shown as done. These steps start fresh. If a teammate is building this same client, you can use their progress instead."));
|
|
@@ -65495,20 +65649,23 @@ function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
|
65495
65649
|
const j=await bsPost("/api/intake-complete",body);bsBusy[key]=false;
|
|
65496
65650
|
if(!j.ok)bsErr[key]=j.error;else{delete bsChange[id+":4"];delete bsStaff[id];delete bsDraft[id+":team"];if(j.approvalWithdrawn)bsNote[id]=j.message;iMode[id]=null;if(iDirty[id])iDirty[id].clear();delete iData[id]}load(true)}));
|
|
65497
65651
|
bsErrLine(key,box);return}
|
|
65498
|
-
|
|
65499
|
-
|
|
65500
|
-
if(n===
|
|
65652
|
+
/* OS audit F11 (command-center ruling (c), 2026-10-30): what the client bought is asked before the Brief, so the
|
|
65653
|
+
Blueprint Review is written for exactly what was sold and the normal order never leaves it out of date. */
|
|
65654
|
+
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}
|
|
65656
|
+
if(n===7){bsStaffStep(c,state,box);return}
|
|
65657
|
+
if(n===8){const a=bsInputs(c,state,null).assessment;
|
|
65501
65658
|
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))}
|
|
65502
65659
|
if(!rData[id])rLoad(id);bsHideAssess=true;try{box.appendChild(unnest(reviewPanel(id,c.name,state,run)))}finally{bsHideAssess=false}return}
|
|
65503
|
-
if(n===
|
|
65660
|
+
if(n===9){const bp=((state.buildPreviews||{})[id]);const live=run&&run.running&&run.locationId===id&&run.stage===3;
|
|
65504
65661
|
if(bp&&!bp.confirmed&&!live){bsPause(c,state,bp.preview,box);return}
|
|
65505
65662
|
bsOutcome(c,state,3,box);
|
|
65506
65663
|
/* Run 8 finding 4: it ended on just "Built." The finish says what was made, counted from the build's own record. */
|
|
65507
65664
|
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");
|
|
65508
65665
|
if(made.length){const by={};for(const x of made)by[x.type]=(by[x.type]||0)+1;const KIND={pipeline:"pipeline",stage:"pipeline stage",field:"custom field",tag:"tag",cv:"custom value",calendar:"calendar",form:"form",email_template:"email template",sms_template:"text template",workflow:"draft automation",user:"staff login"};
|
|
65509
65666
|
box.appendChild(mk("p","","Made in GoHighLevel this build: "+Object.entries(by).map(([k,n])=>n+" "+plural(n,KIND[k]||k)).join(", ")+". Every automation is a draft until you switch it on."))}}if(!live)bsSnippets(c,state,box);bsRun(c,state,run,3,(((state.clients[id]||{}).stages||[])[3]==="done")?"Build again":"Start the build",box);return}
|
|
65510
|
-
if(n===
|
|
65511
|
-
if(n===
|
|
65667
|
+
if(n===10){bsOutcome(c,state,4,box);bsRun(c,state,run,4,(state.outcomes||{})[id+":4"]?"Run the check again":"Run the check",box);if((state.outcomes||{})[id+":4"])bsRebuild(c,state,run,box);return}
|
|
65668
|
+
if(n===11){const jobs=bsJobs(c,state);if(jobs)box.appendChild(jobs);
|
|
65512
65669
|
/* The handoff panel is here as well as in step 12: with a chosen home, its publish job blocks step 11 and only
|
|
65513
65670
|
this panel clears it (round-3 review). With no home chosen it is a later job (option A, 2026-10-02). */
|
|
65514
65671
|
const view=((state.buildViews||{})[id])||{};if(view.handoff)box.appendChild(unnest(handoffPanel(c,view)));
|
|
@@ -65517,7 +65674,7 @@ function bsBody(n,c,state,run,box){const id=c.locationId;const key="s"+n+":"+id;
|
|
|
65517
65674
|
if(left5){const off=mk("button","cds-btn sbig","Confirm these tasks are finished");off.type="button";off.disabled=true;box.appendChild(off);box.appendChild(mk("p","muted",left5+" "+plural(left5,"job")+" before sign-off still open. The button turns on when none are left."))}
|
|
65518
65675
|
else bsGate(c,state,5,"Confirm these tasks are finished",box);
|
|
65519
65676
|
bsRecheck(c,state,run,box);return}
|
|
65520
|
-
if(n===
|
|
65677
|
+
if(n===12){const view=((state.buildViews||{})[id])||{};
|
|
65521
65678
|
/* Jerry 2026-10-03: Sign-Off, not Go-Live. Say what Command OS did, then what the person does by hand. */
|
|
65522
65679
|
box.appendChild(bsSignOffList(c,state));
|
|
65523
65680
|
if(view.handoff)box.appendChild(unnest(handoffPanel(c,view)));bsGate(c,state,6,"Sign off this build",box);bsRecheck(c,state,run,box);return}}
|
|
@@ -65532,7 +65689,18 @@ function bsPause(c,state,pv,box){const id=c.locationId;const key="confirm:"+id;
|
|
|
65532
65689
|
/* Ticks belong to one list: a new list starts unticked, and only keys on the list shown are sent (review of a38ebe1). */
|
|
65533
65690
|
if(!bsAllow[id]||bsAllow[id].fp!==pv.fingerprint)bsAllow[id]={fp:pv.fingerprint,keys:{}};const allow=bsAllow[id].keys;
|
|
65534
65691
|
box.appendChild(mk("h3","","Before Build writes anything"));
|
|
65692
|
+
/* OS audit F9 (command-center ruling, 2026-10-30): a build of a different plan left items this build would build on
|
|
65693
|
+
top of. No building on top: undo that build first (Claude lists what it removes and waits for DELETE), or stop. */
|
|
65694
|
+
if((pv.earlierBuild||[]).length){const eb=pv.earlierBuild,cn=c.name||"This account",runs=[...new Set(eb.map(e=>e.runId))];
|
|
65695
|
+
const w=mk("div","cds-callout warn bs-earlier");w.setAttribute("role","alert");
|
|
65696
|
+
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
|
+
w.appendChild(mk("b","","Undo the earlier build: ask Claude"));
|
|
65698
|
+
for(const r of runs){const q=mk("p","say","Undo build run "+r+" for "+cn);w.appendChild(q)}
|
|
65699
|
+
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
|
+
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}
|
|
65535
65702
|
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
|
+
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."));
|
|
65536
65704
|
/* Walk 2026-10-28 W13/W8: "17 emails, 8 email templates" said nothing about the difference, and the list named nothing.
|
|
65537
65705
|
Each kind says what it is, and opens to the name of every object of that kind. */
|
|
65538
65706
|
const kindWord=(k,n)=>{const w=BS_KIND[k]||k;const sp=w.indexOf(" inside ");return n===1?w:(sp>0?w.slice(0,sp)+"s"+w.slice(sp):w+"s")};
|
|
@@ -65747,7 +65915,7 @@ function bsJobs(c,state){const mt=((state.manualTasks||{})[c.locationId])||[];co
|
|
|
65747
65915
|
else if(readJob&&g.timing!=="at-go-live"){
|
|
65748
65916
|
/* Walk W21: what the check last found, and what to do about it. */
|
|
65749
65917
|
const found=why?" "+String(why).replace(/^GoHighLevel shows /,"GoHighLevel still shows "):" The check reads this in GoHighLevel and has not seen it done yet.";
|
|
65750
|
-
note("Waiting for the check:"+found+(/ empty\.$/.test(String(why||""))?" Set it":" Do it in GoHighLevel")+", then press Run the check again in step
|
|
65918
|
+
note("Waiting for the check:"+found+(/ empty\.$/.test(String(why||""))?" Set it":" Do it in GoHighLevel")+", then press Run the check again in step 11, Verify.")}
|
|
65751
65919
|
else if(cb.checked&&g.items.some(t=>t.blocks))note("Ticked. The check confirms it next time it runs.");return row};
|
|
65752
65920
|
const must=groups.filter(g=>g.timing==="before"),on=groups.filter(g=>g.timing==="at-go-live"),later=groups.filter(g=>g.timing==="later");
|
|
65753
65921
|
const left=must.filter(g=>g.items.some(t=>t.blocks!==undefined?t.blocks:!t.claimed)).length;
|
|
@@ -65805,8 +65973,8 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
|
|
|
65805
65973
|
/* A Change closes itself once that step is done with a different answer (review of 64da59d, LOW 15). */
|
|
65806
65974
|
if(bsChange[ck]&&pr.done[i]&&typeof bsChange[ck]==="string"&&bsSummary(i,c,state)!==bsChange[ck])delete bsChange[ck];
|
|
65807
65975
|
/* Step 7 stays open while what the last Add them for me said, or its refusal, is there to read (3.90.1). */
|
|
65808
|
-
if(i===
|
|
65809
|
-
const staffNews=i===
|
|
65976
|
+
if(i===7)bsRevealPasswords(id,(state.staffChecks||{})[id]||bsStaff[id]);
|
|
65977
|
+
const staffNews=i===7&&(((bsStaffSaid[id]||[]).length>0)||Boolean(bsErr["staff:"+id]));
|
|
65810
65978
|
const li=mk("li","bs-step");li.id="step-"+(i+1);const open=(i===pr.current)||Boolean(bsChange[ck])||staffNews;
|
|
65811
65979
|
const num=mk("span","bs-num",String(i+1));num.setAttribute("aria-hidden","true");li.appendChild(num);const main=mk("div","bs-main");li.appendChild(main);
|
|
65812
65980
|
if(open){li.className+=" bs-now";main.appendChild(mk("div","bs-kicker","Step "+(i+1)+" of "+n+(i===pr.current?" · do this now":" · changing")));main.appendChild(mk("h3","bs-title",BS_NAMES[i]));main.appendChild(mk("p","muted",BS_ABOUT[i]));
|
|
@@ -65825,8 +65993,8 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
|
|
|
65825
65993
|
ch.onclick=()=>{bsChange[ck]=bsSummary(i,c,state);render(lastState,lastRun)};main.appendChild(ch);
|
|
65826
65994
|
if(i>=2)main.appendChild(mk("p","muted bs-warn","Saving a change here means the steps after it run again."))}
|
|
65827
65995
|
/* Step 8 can be taken back until Build runs (review of 64da59d: a team change after Approve had no way back). */
|
|
65828
|
-
if(i===
|
|
65829
|
-
un.onclick=()=>icShow(un,uk,{title:"Undo the approval for "+c.name,text:["Step
|
|
65996
|
+
if(i===8&&idle&&!pr.done[9]){const un=mk("button","cds-link","Undo the approval");un.type="button";const uk="unapprove:"+id;
|
|
65997
|
+
un.onclick=()=>icShow(un,uk,{title:"Undo the approval for "+c.name,text:["Step 9 opens again. Nothing is built until you approve again."],ok:"Undo the approval",busyLabel:"Undoing…",
|
|
65830
65998
|
run:async()=>{const j=await bsPost("/api/build-approval",{locationId:id,revoke:true});return j.ok?"":j.error},after:async()=>{load(true)}});
|
|
65831
65999
|
main.appendChild(icAfter(un,uk))}}
|
|
65832
66000
|
else{li.className+=" bs-locked";const t=mk("div","bs-row");t.appendChild(mk("b","bs-title",BS_NAMES[i]));t.appendChild(chip("","After step "+i));main.appendChild(t);main.appendChild(mk("p","muted",BS_ABOUT[i]))}
|
|
@@ -65839,7 +66007,7 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
|
|
|
65839
66007
|
if(view.approvedAt)d.appendChild(mk("p","","Building approved on "+niceDate(view.approvedAt,true)+"."));page.appendChild(d)}}
|
|
65840
66008
|
/* The step that just opened comes into view, once per change (and on first open), after the redraw keeps its scroll. */
|
|
65841
66009
|
if(bsSeen[id]!==pr.current){bsSeen[id]=pr.current;if(pr.current>0)setTimeout(()=>{const el=document.getElementById("step-"+(pr.current+1));if(el)try{el.scrollIntoView({block:"start",behavior:"smooth"})}catch(e){el.scrollIntoView()}},0)}
|
|
65842
|
-
if(pr.current<0){page.appendChild(mk("p","cds-callout info","All "+n+" steps are done. The build is signed off. Switching the client on is yours to do in GoHighLevel: see step
|
|
66010
|
+
if(pr.current<0){page.appendChild(mk("p","cds-callout info","All "+n+" steps are done. The build is signed off. Switching the client on is yours to do in GoHighLevel: see step 13's list."));
|
|
65843
66011
|
/* Walk W26 (2026-10-29): after sign-off nothing could start the build again, and the page still said Built after its
|
|
65844
66012
|
objects were deleted in GoHighLevel. The check can always run again; when it finds built objects gone, it says how
|
|
65845
66013
|
many and Build again is offered beside it. */
|
|
@@ -65968,7 +66136,7 @@ ${cockpitHeaderHtml("agency")}
|
|
|
65968
66136
|
</div>
|
|
65969
66137
|
<div class="ap-save"><span id="msgTop" role="status"></span><button type="submit" form="f" class="cds-btn" data-msg="msgTop">Save your agency details</button></div>
|
|
65970
66138
|
</div>
|
|
65971
|
-
${p.agencyName ? "" : ` <div class="cds-callout warn"><span class="ttl">Add an agency name to use this on client documents.</span>Everything else can wait. The name is what every report and proposal carries.</div>
|
|
66139
|
+
${p.agencyName ? "" : ` <div class="cds-callout warn agency-name-nudge"><span class="ttl">Add an agency name to use this on client documents.</span>Everything else can wait. The name is what every report and proposal carries.</div>
|
|
65972
66140
|
`}
|
|
65973
66141
|
<form id="f">
|
|
65974
66142
|
<div class="cds-grid two">
|
|
@@ -66274,7 +66442,7 @@ document.getElementById("f").addEventListener("submit",async e=>{
|
|
|
66274
66442
|
if(!r.ok||(j&&j.ok===false)){m.textContent=(j&&j.error)||"That did not save, and nothing was changed.";return}
|
|
66275
66443
|
const pkgNote=settlePackages(j&&j.profile,pkgRead);
|
|
66276
66444
|
const chatOk=await saveChatCreation();
|
|
66277
|
-
m.textContent=(j&&j.usable?"Saved.":"Saved. Add an agency name to use this on client documents.")+pkgNote+(chatOk?"":" The chat creation setting did not save. Try again.");
|
|
66445
|
+
/* OS audit F15: the nudge stayed beside "Saved." until a reload; it goes once the name is saved. */if(j&&j.usable)document.querySelectorAll(".agency-name-nudge").forEach(function(el){el.remove()});m.textContent=(j&&j.usable?"Saved.":"Saved. Add an agency name to use this on client documents.")+pkgNote+(chatOk?"":" The chat creation setting did not save. Try again.");
|
|
66278
66446
|
});
|
|
66279
66447
|
|
|
66280
66448
|
/* Each client saves on its own. One record at a time is the whole safety story
|
|
@@ -68836,7 +69004,7 @@ ${yours}
|
|
|
68836
69004
|
<p>Give us the client's website and we read its colors, fonts and logo into a brand you can check and change.</p>
|
|
68837
69005
|
<form id="extract" class="cds-stack bp-form">
|
|
68838
69006
|
<div class="cds-field"><label for="url">Website</label><input class="cds-input" id="url" name="url" type="url" placeholder="https://clinic.example" required autocomplete="url"></div>
|
|
68839
|
-
<div class="cds-field"><label for="rights">Whose site is it?</label><select class="cds-select" id="rights" name="rights"><option value="client-authorized">My client's, and they asked for this</option><option value="own">Mine</option><option value="written-permission">I have written permission</option><option value="none">None of these</option></select></div>
|
|
69007
|
+
<div class="cds-field"><label for="rights">Whose site is it?</label><select class="cds-select" id="rights" name="rights" required><option value="" selected disabled>Choose one</option><option value="client-authorized">My client's, and they asked for this</option><option value="own">Mine</option><option value="written-permission">I have written permission</option><option value="none">None of these</option></select></div>
|
|
68840
69008
|
<div class="cds-field"><label for="name">Name (optional)</label><div class="hint" id="name-hint">What to call this brand here. Left empty, we take it from the site's title.</div><input class="cds-input" id="name" name="name" type="text" autocomplete="off" aria-describedby="name-hint"></div>
|
|
68841
69009
|
<label class="cds-check"><input type="checkbox" id="useBrowser"><span>Also open the site in a browser on this computer<small>Tick this when the first try finds almost nothing. Some sites only draw themselves in a browser. It takes longer.</small></span></label>
|
|
68842
69010
|
<div class="cds-row"><button type="submit" class="cds-btn ghost" id="extract-btn">Pull the brand</button></div>
|
|
@@ -68868,7 +69036,7 @@ fromHash();
|
|
|
68868
69036
|
const rf=document.getElementById("ref-filter");
|
|
68869
69037
|
if(rf)rf.addEventListener("input",()=>{const q=rf.value.trim().toLowerCase();let n=0;for(const b of document.querySelectorAll("[data-look]")){const hit=!q||b.textContent.toLowerCase().includes(q);b.hidden=!hit;if(hit)n++}const c=document.getElementById("ref-count");if(c)c.textContent=q?(n?n+" found":"No look by that name"):""});
|
|
68870
69038
|
})();
|
|
68871
|
-
document.getElementById("extract").addEventListener("submit",async(e)=>{e.preventDefault();const b=document.getElementById("extract-btn");b.disabled=true;say("extract-status","Reading the site\u2026 this takes a few seconds, longer with the browser pass.");try{const j=await post("/api/brand/extract",{url:document.getElementById("url").value,rights:document.getElementById("rights").value,name:document.getElementById("name").value||undefined,useBrowser:document.getElementById("useBrowser").checked});say("extract-status","Stored as "+j.brand.slug+". Opening the card\u2026","ok");location.href="/brand/"+encodeURIComponent(j.brand.slug)}catch(err){say("extract-status",err.message,"err")}finally{b.disabled=false}});
|
|
69039
|
+
document.getElementById("extract").addEventListener("submit",async(e)=>{e.preventDefault();/* OS audit F7: a permission is never answered for the member. */if(!document.getElementById("rights").value){say("extract-status","Choose whose site it is first.","err");return}const b=document.getElementById("extract-btn");b.disabled=true;say("extract-status","Reading the site\u2026 this takes a few seconds, longer with the browser pass.");try{const j=await post("/api/brand/extract",{url:document.getElementById("url").value,rights:document.getElementById("rights").value,name:document.getElementById("name").value||undefined,useBrowser:document.getElementById("useBrowser").checked});say("extract-status","Stored as "+j.brand.slug+". Opening the card\u2026","ok");location.href="/brand/"+encodeURIComponent(j.brand.slug)}catch(err){say("extract-status",err.message,"err")}finally{b.disabled=false}});
|
|
68872
69040
|
for(const btn of document.querySelectorAll("[data-look]")){btn.addEventListener("click",async()=>{btn.disabled=true;say("look-status","Fetching "+btn.textContent+"\u2026");try{const j=await post("/api/brand/import-look",{look:btn.dataset.look});say("look-status","Imported as "+j.brand.slug+". Opening the card\u2026","ok");location.href="/brand/"+encodeURIComponent(j.brand.slug)}catch(err){say("look-status",err.message,"err");btn.disabled=false}})}
|
|
68873
69041
|
for(const btn of document.querySelectorAll("[data-house]")){btn.addEventListener("click",async()=>{const as=prompt("Store this look under which name? (letters, digits, dashes)",btn.dataset.house+"-copy");if(!as)return;btn.disabled=true;try{const j=await post("/api/brand/set",{slug:btn.dataset.house,as});location.href="/brand/"+encodeURIComponent(j.brand.slug)}catch(err){alert(err.message);btn.disabled=false}})}
|
|
68874
69042
|
`)}`;
|
|
@@ -69007,7 +69175,10 @@ document.getElementById("make").addEventListener("submit",async function(e){
|
|
|
69007
69175
|
var run=s.run||{};
|
|
69008
69176
|
var design=run.ok?"design PASS on pass "+run.greenAt:"design NOT GREEN";
|
|
69009
69177
|
var copy=run.copy?(run.copy.error?" \xB7 copy not graded":" \xB7 copy "+run.copy.verdict+(run.copy.firstBreakdown?" (first breakdown: "+run.copy.firstBreakdown+")":"")):"";
|
|
69010
|
-
|
|
69178
|
+
var saved=String(run.file||s.outDir||"").split(/[\\\\/]/).filter(Boolean).pop()||"";
|
|
69179
|
+
say("mk-status",s.status==="done"?design+copy+(saved?" \xB7 saved as "+saved:""):"The run stopped: "+design+copy,s.status==="done"&&run.ok?"ok":"err");
|
|
69180
|
+
/* OS audit F10: a way to open what was made. */
|
|
69181
|
+
if(s.status==="done"&&saved&&/.html?$/i.test(saved)){var st=document.getElementById("mk-status");var a=document.createElement("a");a.className="cds-btn";a.href="/design/result?id="+encodeURIComponent(runId);a.target="_blank";a.rel="noopener";a.textContent="Open the result";st.appendChild(document.createTextNode(" "));st.appendChild(a)}
|
|
69011
69182
|
}
|
|
69012
69183
|
},4000);
|
|
69013
69184
|
}catch(err){stopPolling();started=0;say("mk-status",err.message,"err");go.disabled=false}
|
|
@@ -70390,11 +70561,11 @@ __export(client_calls_exports, {
|
|
|
70390
70561
|
function gateAnalysis(transcript, analysis) {
|
|
70391
70562
|
const rejected = [];
|
|
70392
70563
|
const kept = {
|
|
70393
|
-
saidTheyWant: (analysis.saidTheyWant ?? []).map(
|
|
70564
|
+
saidTheyWant: (analysis.saidTheyWant ?? []).map(clean5).filter(Boolean),
|
|
70394
70565
|
actuallyNeed: [],
|
|
70395
70566
|
unnamedProblems: [],
|
|
70396
70567
|
recommendedSolutions: [],
|
|
70397
|
-
nextCallQuestions: (analysis.nextCallQuestions ?? []).map(
|
|
70568
|
+
nextCallQuestions: (analysis.nextCallQuestions ?? []).map(clean5).filter(Boolean)
|
|
70398
70569
|
};
|
|
70399
70570
|
const check = (section2, item, quote2) => {
|
|
70400
70571
|
if (!item) {
|
|
@@ -70410,17 +70581,17 @@ function gateAnalysis(transcript, analysis) {
|
|
|
70410
70581
|
};
|
|
70411
70582
|
for (const section2 of ["actuallyNeed", "unnamedProblems"]) {
|
|
70412
70583
|
for (const p of analysis[section2] ?? []) {
|
|
70413
|
-
const point =
|
|
70584
|
+
const point = clean5(p?.point), quote2 = String(p?.quote ?? "").trim();
|
|
70414
70585
|
if (check(section2, point, quote2)) kept[section2].push({ point, quote: quote2 });
|
|
70415
70586
|
}
|
|
70416
70587
|
}
|
|
70417
70588
|
for (const s of analysis.recommendedSolutions ?? []) {
|
|
70418
|
-
const solution =
|
|
70589
|
+
const solution = clean5(s?.solution), quote2 = String(s?.quote ?? "").trim();
|
|
70419
70590
|
if (!SOLUTION_KINDS.includes(s?.kind)) {
|
|
70420
70591
|
rejected.push({ section: "recommendedSolutions", item: solution, quote: quote2, why: `"${String(s?.kind)}" is not a kind; use ${SOLUTION_KINDS.join(", ")}.` });
|
|
70421
70592
|
continue;
|
|
70422
70593
|
}
|
|
70423
|
-
if (check("recommendedSolutions", solution, quote2)) kept.recommendedSolutions.push({ solution, kind: s.kind, why:
|
|
70594
|
+
if (check("recommendedSolutions", solution, quote2)) kept.recommendedSolutions.push({ solution, kind: s.kind, why: clean5(s?.why), quote: quote2 });
|
|
70424
70595
|
}
|
|
70425
70596
|
return { kept, rejected };
|
|
70426
70597
|
}
|
|
@@ -70739,7 +70910,7 @@ async function saveToContact(input, deps, gated, source) {
|
|
|
70739
70910
|
if (accepted.length === 0) {
|
|
70740
70911
|
return done({ status: "NOTHING TO SAVE", reason: effective.held.length ? "every answer is waiting for your OK (numbers always do), so nothing new was written yet." : "no new answer passed the quote check, so nothing new was written.", answersOnContact: record?.state.ans ?? null });
|
|
70741
70912
|
}
|
|
70742
|
-
const biz = record?.state.biz ||
|
|
70913
|
+
const biz = record?.state.biz || clean5(input.businessName) || out.businessName || out.contactName;
|
|
70743
70914
|
if (!biz) return fail("neither the contact nor a saved assessment has a business name. Give businessName and try again.");
|
|
70744
70915
|
out.businessName = out.businessName ?? biz;
|
|
70745
70916
|
const skip = { ...record?.state.skip ?? {} };
|
|
@@ -70836,7 +71007,7 @@ async function processClientCall(input, deps) {
|
|
|
70836
71007
|
To confirm:
|
|
70837
71008
|
${confirmList(gated.held).map((l) => `- ${l}`).join("\n")}
|
|
70838
71009
|
${CONFIRM_INSTRUCTION}`;
|
|
70839
|
-
const clientName = save?.businessName ?? save?.contactName ??
|
|
71010
|
+
const clientName = save?.businessName ?? save?.contactName ?? clean5(input.businessName);
|
|
70840
71011
|
const confirmPass = (input.confirmed?.length ?? 0) > 0;
|
|
70841
71012
|
const clientFile = clientName ? buildClientFile({ clientName, date, source: input.source, gated, analysis, transcript: input.transcript, saveLine, confirmedByOwner: save?.confirmedByOwner }) : null;
|
|
70842
71013
|
if (clientFile && confirmPass) {
|
|
@@ -70878,7 +71049,7 @@ ${body}
|
|
|
70878
71049
|
localCopy
|
|
70879
71050
|
};
|
|
70880
71051
|
}
|
|
70881
|
-
var fs50, path50, RECORDERS, SOLUTION_KINDS,
|
|
71052
|
+
var fs50, path50, RECORDERS, SOLUTION_KINDS, clean5, ANALYSIS_MIN_WORDS, CLIENT_FILES_ROOT, WINDOWS_RESERVED, bullet, q2, CONFIRM_INSTRUCTION, stop2, EXISTING_WITH_CONTACT, UNCLEAN_RECORD, contactLocks, same, plural2;
|
|
70882
71053
|
var init_client_calls = __esm({
|
|
70883
71054
|
"src/command-os/client-calls.ts"() {
|
|
70884
71055
|
"use strict";
|
|
@@ -70890,7 +71061,7 @@ var init_client_calls = __esm({
|
|
|
70890
71061
|
init_assessment();
|
|
70891
71062
|
RECORDERS = ["Plaud", "Zoom", "Other"];
|
|
70892
71063
|
SOLUTION_KINDS = ["GoHighLevel build", "AI tool", "custom build", "process"];
|
|
70893
|
-
|
|
71064
|
+
clean5 = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
70894
71065
|
ANALYSIS_MIN_WORDS = 3;
|
|
70895
71066
|
CLIENT_FILES_ROOT = ["Command OS", "Clients"];
|
|
70896
71067
|
WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
@@ -71585,8 +71756,29 @@ function composePreview(input) {
|
|
|
71585
71756
|
else reuse.push({ key: key2, type: item.type, name: item.name, id: item.existingId });
|
|
71586
71757
|
}
|
|
71587
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);
|
|
71588
71769
|
const core = { locationId: input.locationId, planHash: input.planHash, reuse, change, logins: [...input.logins ?? []] };
|
|
71589
|
-
return {
|
|
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
|
+
};
|
|
71590
71782
|
}
|
|
71591
71783
|
function allowedWorkflowIds(allow) {
|
|
71592
71784
|
return new Set(allow.filter((k) => k.startsWith("workflows:")).map((k) => k.slice("workflows:".length)));
|
|
@@ -71606,6 +71798,7 @@ function boardAllowed(preview, confirm2) {
|
|
|
71606
71798
|
}
|
|
71607
71799
|
function confirmMatches(confirm2, preview) {
|
|
71608
71800
|
if (!previewNamesLogins(preview)) return false;
|
|
71801
|
+
if (preview.earlierBuild?.length) return false;
|
|
71609
71802
|
if (!confirm2 || confirm2.fingerprint !== preview.fingerprint || !Array.isArray(confirm2.allow)) return false;
|
|
71610
71803
|
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
71611
71804
|
return confirm2.allow.every((k) => changeKeys.has(k));
|
|
@@ -71680,6 +71873,14 @@ function listedValue(raw, id2) {
|
|
|
71680
71873
|
const value = hit && typeof hit === "object" ? Reflect.get(hit, "value") : void 0;
|
|
71681
71874
|
return typeof value === "string" ? value : void 0;
|
|
71682
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
|
+
}
|
|
71683
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;
|
|
71684
71885
|
var init_build_confirm = __esm({
|
|
71685
71886
|
"src/build-confirm.ts"() {
|
|
@@ -72192,6 +72393,7 @@ __export(dashboard_exports, {
|
|
|
72192
72393
|
forgetVerifiedHandoff: () => forgetVerifiedHandoff,
|
|
72193
72394
|
guarded: () => guarded,
|
|
72194
72395
|
hasChosenScope: () => hasChosenScope,
|
|
72396
|
+
industryChoices: () => industryChoices,
|
|
72195
72397
|
insideDir: () => insideDir,
|
|
72196
72398
|
intakeAskFacts: () => intakeAskFacts,
|
|
72197
72399
|
intakeErrorDetail: () => intakeErrorDetail,
|
|
@@ -72217,6 +72419,7 @@ __export(dashboard_exports, {
|
|
|
72217
72419
|
memberFacingError: () => memberFacingError,
|
|
72218
72420
|
mergeQueueIntakes: () => mergeQueueIntakes,
|
|
72219
72421
|
newClientFailure: () => newClientFailure,
|
|
72422
|
+
noClientPageHtml: () => noClientPageHtml,
|
|
72220
72423
|
normalizedIntake: () => normalizedIntake,
|
|
72221
72424
|
notPreparedHtml: () => notPreparedHtml,
|
|
72222
72425
|
notSignedInMsg: () => notSignedInMsg,
|
|
@@ -72904,7 +73107,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
72904
73107
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
72905
73108
|
const shared = { at, locationId: locationId2, stage, engineVersion: outcome.engineVersion, engineModel: outcome.engineModel };
|
|
72906
73109
|
if (outcome.engineCheckRan || outcome.engineCheckCostUsd !== void 0) {
|
|
72907
|
-
recordRunCost({ ...shared, ok:
|
|
73110
|
+
recordRunCost({ ...shared, ok: outcome.engineCheckOk !== false, kind: "engine-check", costUsd: outcome.engineCheckCostUsd });
|
|
72908
73111
|
}
|
|
72909
73112
|
recordRunCost({
|
|
72910
73113
|
...shared,
|
|
@@ -72930,6 +73133,9 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
72930
73133
|
outcome.issues = [];
|
|
72931
73134
|
outcome.plain = { summary: BRIEF_NEEDS_INTAKE2, issues: [] };
|
|
72932
73135
|
}
|
|
73136
|
+
const noted = failureNoteFor(stage, outcome, run.lines, scopeRefusal || briefNeedsIntake);
|
|
73137
|
+
const failNote = noted !== null;
|
|
73138
|
+
if (noted) Object.assign(outcome, noted);
|
|
72933
73139
|
if (stage === 3 && !outcome.summary && !outcome.issues?.length) {
|
|
72934
73140
|
try {
|
|
72935
73141
|
const st = readCockpitState();
|
|
@@ -72945,8 +73151,8 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
72945
73151
|
const st = readCockpitState();
|
|
72946
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 }) } } });
|
|
72947
73153
|
};
|
|
72948
|
-
save(scopeRefusal || briefNeedsIntake ? outcome.plain : void 0);
|
|
72949
|
-
if (PLAIN_REPORT_STAGES.has(stage) && !scopeRefusal && !briefNeedsIntake) {
|
|
73154
|
+
save(scopeRefusal || briefNeedsIntake || failNote ? outcome.plain : void 0);
|
|
73155
|
+
if (PLAIN_REPORT_STAGES.has(stage) && !scopeRefusal && !briefNeedsIntake && !failNote) {
|
|
72950
73156
|
run.lines.push("Writing the plain-English version of what came back\u2026");
|
|
72951
73157
|
const rewritten = await rewriteOutcomePlain(STAGES2[stage], verifyRaw ?? outcome, { onEvent: (l) => run.lines.push(l), onCost: (costUsd) => {
|
|
72952
73158
|
recordRunCost({ at, locationId: locationId2, stage, kind: "plain rewrite", ok: true, costUsd });
|
|
@@ -73100,6 +73306,10 @@ async function cockpitAlreadyRunning(port) {
|
|
|
73100
73306
|
return false;
|
|
73101
73307
|
}
|
|
73102
73308
|
}
|
|
73309
|
+
function industryChoices() {
|
|
73310
|
+
const packs = INDUSTRY_PACKS.map((p) => ({ slug: p.slug, label: p.label }));
|
|
73311
|
+
return packs.some((p) => p.slug === "other") ? packs : [...packs, { slug: "other", label: "Other" }];
|
|
73312
|
+
}
|
|
73103
73313
|
function renderCockpitHtml(roster, seatToken = "", screen = "board", cap2) {
|
|
73104
73314
|
return html(roster, seatToken, screen, cap2);
|
|
73105
73315
|
}
|
|
@@ -73461,8 +73671,8 @@ function intakeRequestError(e) {
|
|
|
73461
73671
|
function intakeErrorDetail(e) {
|
|
73462
73672
|
const raw = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
73463
73673
|
if (!raw || INTAKE_OWN_MESSAGES.includes(raw)) return void 0;
|
|
73464
|
-
const
|
|
73465
|
-
return
|
|
73674
|
+
const clean6 = raw.replace(/(?:[A-Za-z]:)?(?:[\\/][^\s"'\\/]+){2,}[\\/]?/g, "[path]").replace(/\b(?:pit|sk|key|token|bearer)[-_ ][A-Za-z0-9._-]{6,}/gi, "[hidden]").replace(/\b[A-Za-z0-9_-]{24,}\b/g, "[hidden]").replace(/\s+/g, " ").trim();
|
|
73675
|
+
return clean6 ? clean6.length > 240 ? clean6.slice(0, 237) + "\u2026" : clean6 : void 0;
|
|
73466
73676
|
}
|
|
73467
73677
|
function intakeRefusal(e) {
|
|
73468
73678
|
const detail = intakeErrorDetail(e);
|
|
@@ -73673,6 +73883,16 @@ ${cockpitHeaderHtml("for", { trail: ["For this client", what] })}
|
|
|
73673
73883
|
${cockpitShellClose()}
|
|
73674
73884
|
<script>${COCKPIT_TYPE_JS}</script></body></html>`;
|
|
73675
73885
|
}
|
|
73886
|
+
function noClientPageHtml(what, why) {
|
|
73887
|
+
const title2 = why === "no-client" ? `Choose a client to see ${what.toLowerCase().replace(/^the /, "their ")}` : "No Blueprint Review yet";
|
|
73888
|
+
const sub = why === "no-client" ? `${what} belongs to one client. Open it from that client's page.` : "Write the Brief for this client first; the Blueprint Review is written with it.";
|
|
73889
|
+
return `${cockpitHeadHtml(`${title2} \xB7 ${productName()}`, { extraCss: "#main{display:grid;gap:var(--space-6);align-content:start;min-width:0}" })}<body data-screen="for">
|
|
73890
|
+
${cockpitHeaderHtml("for", { trail: ["For this client", what] })}
|
|
73891
|
+
<main id="main" class="no-client"><div class="cds-page-head"><div class="txt"><div class="cds-kicker">For this client</div><h1 class="cds-page-title">${title2}</h1><p class="cds-page-sub">${sub}</p></div></div>
|
|
73892
|
+
<div class="cds-empty"><p><a class="cds-btn" href="/">Back to the start</a></p></div></main>
|
|
73893
|
+
${cockpitShellClose()}
|
|
73894
|
+
<script>${COCKPIT_TYPE_JS}</script></body></html>`;
|
|
73895
|
+
}
|
|
73676
73896
|
function unknownCapabilityHtml() {
|
|
73677
73897
|
return `${cockpitHeadHtml("No such page \xB7 Command OS", { extraCss: "#main{display:grid;gap:var(--space-6);align-content:start;min-width:0}" })}<body data-screen="for">
|
|
73678
73898
|
${cockpitHeaderHtml("for", { trail: ["For this client", "Page not found"] })}
|
|
@@ -73898,7 +74118,7 @@ function followRefresh(id,n){if(n>20)return;const tok=clientToken();setTimeout(a
|
|
|
73898
74118
|
async function refreshSnapshots(){const tok=clientToken();const sr=await(await fetch("/api/snapshots")).json();if(!sr||!sr.ok)return;if(sr.cache)ageViews.snapshots=sr.cache;
|
|
73899
74119
|
if(!stillFor(tok))return;
|
|
73900
74120
|
if(JSON.stringify(sr.snapshots)!==JSON.stringify(snapshots)){snapshots=sr.snapshots;if(!userIsBusy()&&!unsavedTyping()&&lastState)renderFor(tok);else redrawPending=true}else paintAges()}
|
|
73901
|
-
const INDUSTRIES=${scriptJson(
|
|
74121
|
+
const INDUSTRIES=${scriptJson(industryChoices())};
|
|
73902
74122
|
const INDUSTRIES2=${scriptJson(INDUSTRY_PACKS.map((p) => ({ slug: p.slug, label: p.label })))};
|
|
73903
74123
|
/* One account at a time (Jerry, 2026-08-26: "way too busy\u2026 tempting to work on multiple
|
|
73904
74124
|
accounts at once"). One card is ever expanded; every other account is a one-line row. */
|
|
@@ -74680,7 +74900,9 @@ function scopeBlock(id,state){
|
|
|
74680
74900
|
/* Run 8 finding 9: about 5 seconds with nothing showing it was working. */
|
|
74681
74901
|
sv.disabled=true;const was=sv.textContent;sv.textContent="Saving\u2026";status.textContent="Saving what they bought\u2026";
|
|
74682
74902
|
const body={locationId:id,bought:[...ticked]};if(sel.value)body.package=sel.value;if(asksDraft[id])body.asks=asksClean(asksDraft[id].a);const r=await postJSON("/api/scope",body);
|
|
74683
|
-
|
|
74903
|
+
/* OS audit F11: the Review panel kept the copy it read before this save, so it never said the Review was now out of
|
|
74904
|
+
date while the client document refused it. Read it again after every save. */
|
|
74905
|
+
if(r){unsaved.hidden=true;delete asksDraft[id];if(r.approvalWithdrawn)boughtNotice[id]=r.message;else delete boughtNotice[id];delete scopeDraft[id];delete assessmentCards[id];status.textContent="Saved.";if(rData[id])rLoad(id);load()}
|
|
74684
74906
|
if(sv.isConnected){sv.disabled=false;sv.textContent=was}};
|
|
74685
74907
|
/* Audit 2026-10-28 #9: the always-built pipeline counts as built ("2 bought, 1 built" while it is always built). */
|
|
74686
74908
|
/* rc.3 c6 hole 2: the count follows the labels: what will be built (or was) and what waits on your client's answer. */
|
|
@@ -74717,15 +74939,19 @@ function reviewPanel(id,name,state,run){const r=rData[id];const d=document.creat
|
|
|
74717
74939
|
if(busy){const p=document.createElement("p");p.className="muted";
|
|
74718
74940
|
p.textContent="Reading their answers and writing the review, usually 1 to 2 minutes. This panel updates itself when it is ready.";d.appendChild(p)}
|
|
74719
74941
|
/* rc.3 c6 hole 1: a review written before the answers changed may promise what the build will not make. */
|
|
74720
|
-
|
|
74942
|
+
/* OS audit F11 (ruling (c)): one plain line, and the two choices on the row below: Approve anyway, or Update the review. */
|
|
74943
|
+
if(r&&r.ok&&r.stale&&!busy){const w=mk("p","cds-callout warn","This review was written before your last change. Approve anyway, or update the review first; the client document waits for an updated one.");w.setAttribute("role","alert");d.appendChild(w)}
|
|
74721
74944
|
if(r&&r.ok&&!r.stale&&r.graded!==true&&!busy){const w=mk("p","cds-callout warn","Not checked for over-promises. Regenerate.");w.setAttribute("role","alert");d.appendChild(w)}
|
|
74722
|
-
|
|
74945
|
+
/* OS audit F12: the trimmed lines were one run-on sentence ("...regular schedule today?.") read first. Now a folded
|
|
74946
|
+
note: how many lines, why, and each line as it was, with nothing added to its punctuation. */
|
|
74947
|
+
if(r&&r.ok&&Array.isArray(r.trimmed)&&r.trimmed.length&&!busy){const n=r.trimmed.length;const det=mk("details","cds-details review-trimmed");det.appendChild(mk("summary","",n+(n===1?" line was":" lines were")+" taken out of this review because "+(n===1?"it":"they")+" promised more than this build makes"));const ul=mk("ul","cds-list");for(const t of r.trimmed){ul.appendChild(mk("li","",String(t)))}det.appendChild(ul);d.appendChild(det)}
|
|
74723
74948
|
if(!r||!r.ok){if(!busy){const p=document.createElement("p");if(r&&r.error&&r.error!=="not generated yet"){p.className="cds-callout warn";p.setAttribute("role","alert")}else p.className="muted";
|
|
74724
74949
|
p.textContent=r&&r.error?r.error:"Turn their intake answers into strategy: who they really sell to, whether the offer holds up, what the account is missing, and what to build first.";
|
|
74725
74950
|
d.appendChild(p)}
|
|
74726
74951
|
d.appendChild(gen);
|
|
74727
|
-
/* What they bought decides the build, so it is here even before the review is written (2026-09-29).
|
|
74728
|
-
|
|
74952
|
+
/* What they bought decides the build, so it is here even before the review is written (2026-09-29). On the steps
|
|
74953
|
+
page it is its own step 6, before the Brief (OS audit F11, ruling (c)), so it is not shown twice. */
|
|
74954
|
+
if(!bsHideAssess)d.appendChild(scopeBlock(id,state));
|
|
74729
74955
|
/* On the steps page Approve never waits for the review: the server does not need it (round-4 review). */
|
|
74730
74956
|
if(bsHideAssess&&scopeChosen(state,id)&&!(state.buildApproved||{})[id]&&!PROTECTED[id])d.appendChild(approveButton(id,name,state,"review","cds-btn"));
|
|
74731
74957
|
return d}
|
|
@@ -74758,7 +74984,7 @@ function reviewPanel(id,name,state,run){const r=rData[id];const d=document.creat
|
|
|
74758
74984
|
s4.appendChild(bl);d.appendChild(s4)}}
|
|
74759
74985
|
if((r.unknowns||[]).length){const s5=document.createElement("div");s5.className="cds-stack";s5.innerHTML="<h4 class='cds-panel-label'>Ask them next</h4>";
|
|
74760
74986
|
const p=document.createElement("p");p.textContent=r.unknowns.join(" \xB7 ");s5.appendChild(p);d.appendChild(s5)}
|
|
74761
|
-
d.appendChild(scopeBlock(id,state));
|
|
74987
|
+
if(!bsHideAssess)d.appendChild(scopeBlock(id,state));
|
|
74762
74988
|
d.appendChild(mk("h4","cds-panel-label","The client document: your agency name and color on it"));const brand=document.createElement("div");brand.className="cds-row brand-row";
|
|
74763
74989
|
const an=document.createElement("input");an.placeholder="Your agency name (on the client doc)";an.value=(state.agencyName||"");
|
|
74764
74990
|
an.className="cds-input";an.setAttribute("aria-label","Your agency name on the client document");
|
|
@@ -74780,6 +75006,7 @@ function reviewPanel(id,name,state,run){const r=rData[id];const d=document.creat
|
|
|
74780
75006
|
if((state.buildApproved||{})[id]&&intakeStale){const ap=document.createElement("button");ap.className="cds-btn ghost";ap.textContent="Approve again once the intake is reviewed";ap.disabled=true;row.appendChild(ap)}
|
|
74781
75007
|
else if((state.buildApproved||{})[id]&&!approvalStale){const ap=document.createElement("button");ap.className="cds-btn ghost";ap.textContent="Approved";ap.disabled=true;row.appendChild(ap)}
|
|
74782
75008
|
else if(scopeChosen(state,id)&&!["template","hidden"].includes(typeOf(state,id))&&!PROTECTED[id])row.appendChild(approveButton(id,name,state,"review","cds-btn"));
|
|
75009
|
+
if(r.stale&&!busy){gen.textContent="Update the review (uses Claude)";const ab=row.querySelector("button.cds-btn:not(.ghost)");if(ab&&/^Approve this plan$/.test(ab.textContent))ab.textContent="Approve anyway"}
|
|
74783
75010
|
row.appendChild(gen);d.appendChild(row);
|
|
74784
75011
|
return d}
|
|
74785
75012
|
const NC_AGENCY=${scriptJson(readAgencyProfile().agencyName?.trim() || "in GoHighLevel")};
|
|
@@ -75563,7 +75790,7 @@ function autoBuildClaim(id,b){if(!autoBuild[id])return;queueMicrotask(()=>{const
|
|
|
75563
75790
|
function approveSpec(id,name,state){const sandbox=typeOf(state,id)==="sandbox";
|
|
75564
75791
|
const view=(state.buildViews||{})[id];const repairs=(view&&view.planHash&&view.repairs)||[];
|
|
75565
75792
|
return {title:"Approve this plan for "+name,
|
|
75566
|
-
text:["Command OS then builds in this account what the Blueprint Review lists"+(repairs.length?", and makes the "+repairs.length+" live workflow "+plural(repairs.length,"repair")+" listed with the plan":"")+".","When you approve, step
|
|
75793
|
+
text:["Command OS then builds in this account what the Blueprint Review lists"+(repairs.length?", and makes the "+repairs.length+" live workflow "+plural(repairs.length,"repair")+" listed with the plan":"")+".","When you approve, step 10 opens. Click Start the build there."],
|
|
75567
75794
|
after:async()=>{load()},
|
|
75568
75795
|
fields:[NAME_FIELD()].concat(sandbox?[]:[{id:"client",label:"Type the client name to confirm: "+name,value:""}]),
|
|
75569
75796
|
ok:"Approve",busyLabel:"Approving\u2026",
|
|
@@ -75867,7 +76094,8 @@ function renderCapability(m,state,run){
|
|
|
75867
76094
|
if(CAP.key==="checkup"){const v=capCheckupPanel(last,bb);capReadsInto(v,c,hasBuild,true);m.appendChild(v)}
|
|
75868
76095
|
else{
|
|
75869
76096
|
/* In this account now, read live from GoHighLevel. */
|
|
75870
|
-
|
|
76097
|
+
/* OS audit F16: a page that reads nothing said "Read live from GoHighLevel" above "This page does not read the account yet". */
|
|
76098
|
+
const card=CAP.reads.length||CAP.key==="handoff"?panel("Read live from GoHighLevel","In this account now"):panel("Not read on this page","Ask Claude below");card.classList.add("cap-reads");
|
|
75871
76099
|
capReadsInto(card,c,hasBuild,false);m.appendChild(card)}
|
|
75872
76100
|
m.appendChild(capDoPanel(c,state,run,words,hasBuild,busy,last));
|
|
75873
76101
|
/* From the last build: only this page's own items. */
|
|
@@ -76003,7 +76231,7 @@ function capOwedPanel(owed,counts,hl){if(!counts){counts={before:0,atGoLive:0,la
|
|
|
76003
76231
|
const jobs=owed.filter(x=>x.job),lines=hl||[];
|
|
76004
76232
|
if(!jobs.length&&!lines.length){p.appendChild(capEmpty("Nothing is recorded as owed yet","The handoff checks and the manual tasks for this client show here once the build has been verified."));return p}
|
|
76005
76233
|
const c=counts||{before:0,atGoLive:0,later:0};
|
|
76006
|
-
p.appendChild(mk("p","",c.before+" "+plural(c.before,"job")+" before sign-off, "+c.atGoLive+" "+plural(c.atGoLive,"automation")+" to switch on, "+c.later+" for later: the same jobs as step
|
|
76234
|
+
p.appendChild(mk("p","",c.before+" "+plural(c.before,"job")+" before sign-off, "+c.atGoLive+" "+plural(c.atGoLive,"automation")+" to switch on, "+c.later+" for later: the same jobs as step 12."));
|
|
76007
76235
|
if(lines.length)p.appendChild(mk("p","muted",handoffCountLine(lines,c)));
|
|
76008
76236
|
p.appendChild(capList(jobs.map(x=>capRow(x.text,"",x.done?chip("ok","Done"):chip(x.timing==="before"?"warn":"",x.timing==="before"?"Before sign-off":x.timing==="at-go-live"?"After sign-off":"For later"))),"jobs"));return p}
|
|
76009
76237
|
/* Intake and documents: what is answered, what is still open, the key facts, and the files. */
|
|
@@ -77549,7 +77777,7 @@ async function runDashboard(argv) {
|
|
|
77549
77777
|
if (other) cockpitCache.invalidate(account2.locationId, ["brief-account"]);
|
|
77550
77778
|
const v = other ? { cold: true, refreshing: v0.refreshing, stale: true } : v0;
|
|
77551
77779
|
seen.push({ account: account2, v });
|
|
77552
|
-
if (v.cold) throw !other && v.lastRefresh?.text ? new Error(v.lastRefresh.text) : new AccountLoading(COLD_TEXT);
|
|
77780
|
+
if (v.cold) throw !other && !v.refreshing && v.lastRefresh?.text ? new Error(v.lastRefresh.text) : new AccountLoading(COLD_TEXT);
|
|
77553
77781
|
return v.data.read;
|
|
77554
77782
|
},
|
|
77555
77783
|
ages: () => {
|
|
@@ -78337,7 +78565,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78337
78565
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
78338
78566
|
const proClient = gate3.plan === "pro" ? new URL(req.url || "/", "http://x").searchParams.get("locationId") ?? "" : void 0;
|
|
78339
78567
|
const mine = activeRun2 && (proClient === void 0 || activeRun2.locationId === proClient);
|
|
78340
|
-
res.end(JSON.stringify(mine && activeRun2 ? { ...activeRun2, lines: memberProgress(activeRun2.lines, activeRun2.running, activeRun2.ok) } : activeRun2?.running ? { running: true, elsewhere: true, lines: [] } : { running: false, lines: [] }));
|
|
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: [] }));
|
|
78341
78569
|
return;
|
|
78342
78570
|
}
|
|
78343
78571
|
if (req.method === "GET" && url === "/api/run/secrets") {
|
|
@@ -78855,6 +79083,17 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78855
79083
|
});
|
|
78856
79084
|
return;
|
|
78857
79085
|
}
|
|
79086
|
+
if (req.method === "GET" && url === "/design/result") {
|
|
79087
|
+
const file3 = designResultFile(new URL(req.url || "/", "http://x").searchParams.get("id") || "");
|
|
79088
|
+
if (!file3) {
|
|
79089
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
79090
|
+
res.end("That design is not ready, or it was not made on this computer.");
|
|
79091
|
+
return;
|
|
79092
|
+
}
|
|
79093
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": "sandbox allow-scripts", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
79094
|
+
res.end(fs53.readFileSync(file3, "utf8"));
|
|
79095
|
+
return;
|
|
79096
|
+
}
|
|
78858
79097
|
if (req.method === "GET" && url === "/api/design/run") {
|
|
78859
79098
|
const id2 = new URL(req.url || "/", "http://x").searchParams.get("id");
|
|
78860
79099
|
const payload = id2 ? designRunStatus(id2) : { runs: listDesignRuns() };
|
|
@@ -78978,8 +79217,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78978
79217
|
if (req.method === "GET" && url === "/handoff") {
|
|
78979
79218
|
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
78980
79219
|
if (!readableAccount(locationId2)) {
|
|
78981
|
-
res.writeHead(400);
|
|
78982
|
-
res.end("
|
|
79220
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
79221
|
+
res.end(noClientPageHtml("The handoff", "no-client"));
|
|
78983
79222
|
return;
|
|
78984
79223
|
}
|
|
78985
79224
|
try {
|
|
@@ -78993,8 +79232,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
78993
79232
|
if (req.method === "GET" && url === "/handoff/detail") {
|
|
78994
79233
|
const locationId2 = new URL(req.url || "/", "http://x").searchParams.get("locationId") || "";
|
|
78995
79234
|
if (!readableAccount(locationId2)) {
|
|
78996
|
-
res.writeHead(400);
|
|
78997
|
-
res.end("
|
|
79235
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
79236
|
+
res.end(noClientPageHtml("The account detail", "no-client"));
|
|
78998
79237
|
return;
|
|
78999
79238
|
}
|
|
79000
79239
|
try {
|
|
@@ -79104,8 +79343,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79104
79343
|
const st = readCockpitState();
|
|
79105
79344
|
const review = st.reviews?.[locationId2];
|
|
79106
79345
|
if (!review?.ok) {
|
|
79107
|
-
res.writeHead(404, { "Content-Type": "text/
|
|
79108
|
-
res.end("
|
|
79346
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
79347
|
+
res.end(noClientPageHtml("The Blueprint Review", readableAccount(locationId2) ? "no-review" : "no-client"));
|
|
79109
79348
|
return;
|
|
79110
79349
|
}
|
|
79111
79350
|
if (reviewIsStale(st, locationId2, review)) {
|
|
@@ -79180,9 +79419,10 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79180
79419
|
const rk = who2.recordKey;
|
|
79181
79420
|
if (PROTECTED_LOCATIONS[who2.locationId]) throw new Error(PROTECTED_LOCATIONS[who2.locationId] + " can never be built.");
|
|
79182
79421
|
const current = readCockpitState();
|
|
79183
|
-
if (!current.buildApproved?.[rk]) throw new Error("Approve building (step
|
|
79422
|
+
if (!current.buildApproved?.[rk]) throw new Error("Approve building (step 9) before confirming the build.");
|
|
79184
79423
|
const preview = readBuildPreview(rk);
|
|
79185
79424
|
if (!preview || !previewNamesLogins(preview) || typeof fingerprint2 !== "string" || preview.fingerprint !== fingerprint2) throw new Error("This list is out of date. Start the build again to see the current one.");
|
|
79425
|
+
if (preview.earlierBuild?.length) throw new Error(earlierBuildText(preview.earlierBuild, who2.name).why);
|
|
79186
79426
|
const changeKeys = new Set(preview.change.map((e) => e.key));
|
|
79187
79427
|
const allowed = Array.isArray(allow) ? [...new Set(allow.filter((k) => typeof k === "string"))] : [];
|
|
79188
79428
|
if (allowed.some((k) => !changeKeys.has(k))) throw new Error("Only items on the list can be changed.");
|
|
@@ -79250,6 +79490,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
79250
79490
|
const approval = { inputHash: hash, at, repairHashes: readWorkflowRepairs(who2.locationId).filter((r) => Array.isArray(repairHashes) && repairHashes.includes(r.hash)).map((r) => r.hash), staffHash: staffListHash(current.staffRoster?.[who2.locationId]), scopeHash: approvalScopeHash(current, rk), boughtHash: approvalBoughtHash(current, rk), ...approvedBy };
|
|
79251
79491
|
approvals[rk] = approval;
|
|
79252
79492
|
}
|
|
79493
|
+
if (!revoke && approvals[rk] && reviewIsStale(current, rk, current.reviews?.[rk])) approvals[rk] = { ...approvals[rk], approvedOverStaleReview: true };
|
|
79253
79494
|
const latest = readCockpitState();
|
|
79254
79495
|
writeCockpitState({ ...setBuildApproval(latest, rk, revoke ? null : at), planApprovals: { ...latest.planApprovals, [rk]: approvals[rk] }, clients: revoke ? latest.clients : setStage(latest, who2.locationId, 2, "done", who2.clientKey).clients });
|
|
79255
79496
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -79958,6 +80199,7 @@ var init_dashboard = __esm({
|
|
|
79958
80199
|
init_client_status_publish();
|
|
79959
80200
|
init_handoff_snapshot();
|
|
79960
80201
|
init_member_progress();
|
|
80202
|
+
init_stage_failure();
|
|
79961
80203
|
init_build_obligations();
|
|
79962
80204
|
fs53 = __toESM(require("fs"));
|
|
79963
80205
|
path53 = __toESM(require("path"));
|
|
@@ -80079,15 +80321,15 @@ var init_dashboard = __esm({
|
|
|
80079
80321
|
CHOOSE_BEFORE_BUILD = "Choose what the client bought before building: Blueprint Review, then What they bought, then Save.";
|
|
80080
80322
|
TEXT_SNIPPETS_LOCKED = "Text snippets are chosen before the first build. This client's build plan is already saved, so the choice stays as it was.";
|
|
80081
80323
|
SCOPE_REFUSAL = /Nobody has chosen what|what this client bought|features (are )?selected for this client|No features are selected|features nobody ticked/i;
|
|
80082
|
-
APPROVAL_WITHDRAWN_FOR_STAFF = "Your team changed after you approved, so the approval was taken back. Approve again in step
|
|
80083
|
-
APPROVAL_WITHDRAWN_NO_TEAM = "This approval was given before the team was saved with it, so it was taken back. Approve again in step
|
|
80324
|
+
APPROVAL_WITHDRAWN_FOR_STAFF = "Your team changed after you approved, so the approval was taken back. Approve again in step 9.";
|
|
80325
|
+
APPROVAL_WITHDRAWN_NO_TEAM = "This approval was given before the team was saved with it, so it was taken back. Approve again in step 9.";
|
|
80084
80326
|
HANDOFF_WORKFLOW = (ref) => ref === "handoff.quote_tag" ? "workflow.quote_follow_up" : ref === "handoff.review_link" || ref.startsWith("handoff.review_tag") ? "workflow.review_request" : void 0;
|
|
80085
80327
|
NOTHING_BOUGHT = "Tick at least one item they bought, then save.";
|
|
80086
80328
|
NOTHING_BUILT_FROM_TICKS = "None of the ticked items is built for this client's industry, so nothing was saved. Tick at least one item marked Built.";
|
|
80087
80329
|
ASSESSMENT_CHANGED_SINCE_APPROVAL = "The Assessment answer or the attached Assessment changed after the build was approved, so Build did not start. Approve this plan again, then build.";
|
|
80088
80330
|
APPROVAL_WITHDRAWN_FOR_ASSESSMENT = "The Assessment answer changed, so the build approval was withdrawn. Approve this plan again before building.";
|
|
80089
80331
|
APPROVE_NEEDS_INTAKE = "Approve opens after step 5, Intake, is finished.";
|
|
80090
|
-
APPROVE_NEEDS_BRIEF = "Approve opens after step
|
|
80332
|
+
APPROVE_NEEDS_BRIEF = "Approve opens after step 7, Brief, has run.";
|
|
80091
80333
|
INDUSTRY_FIRST = "Choose what kind of business this is in step 3 first.";
|
|
80092
80334
|
NOTHING_TICKED_IN_PLAN = "None of the ticked features is in the plan for this client's industry, so nothing was saved. Tick features from the list, or choose Everything in the plan.";
|
|
80093
80335
|
SCOPE_CHANGED_SINCE_APPROVAL = "What they bought changed after the build was approved, so Build did not start. Approve this plan again, then build.";
|
|
@@ -86968,14 +87210,14 @@ function registerDesignStudioTools(server2) {
|
|
|
86968
87210
|
d.provenance.evidence["shape.radius"] = `set by ${stamp}`;
|
|
86969
87211
|
}
|
|
86970
87212
|
if (a.logoSvg) {
|
|
86971
|
-
let
|
|
87213
|
+
let clean6;
|
|
86972
87214
|
try {
|
|
86973
|
-
|
|
87215
|
+
clean6 = sanitizeSvg(a.logoSvg);
|
|
86974
87216
|
} catch (e) {
|
|
86975
87217
|
throw new Error(`${e.message} Nothing was changed.`);
|
|
86976
87218
|
}
|
|
86977
|
-
a.logoSvg =
|
|
86978
|
-
d.logo = { inlineSvg:
|
|
87219
|
+
a.logoSvg = clean6;
|
|
87220
|
+
d.logo = { inlineSvg: clean6, whiteFillFixed: false, source: `set by ${stamp}`, rights: "declared" };
|
|
86979
87221
|
d.provenance.evidence["logo"] = `set by ${stamp}`;
|
|
86980
87222
|
}
|
|
86981
87223
|
if (a.tone) d.voice.tone = a.tone;
|
|
@@ -90652,8 +90894,8 @@ async function listProcs(platform2 = process.platform, exec = execText, timeoutM
|
|
|
90652
90894
|
return listingOf(platform2, await bounded(exec(file3, args, timeoutMs), timeoutMs));
|
|
90653
90895
|
}
|
|
90654
90896
|
function jobMarker(jobDir, platform2 = process.platform) {
|
|
90655
|
-
const
|
|
90656
|
-
return `--user-data-dir=${jobDir.replace(/[\\/]+$/, "")}${
|
|
90897
|
+
const sep7 = platform2 === "win32" ? "\\" : "/";
|
|
90898
|
+
return `--user-data-dir=${jobDir.replace(/[\\/]+$/, "")}${sep7}`;
|
|
90657
90899
|
}
|
|
90658
90900
|
function holdsMarker(command, marker, platform2 = process.platform) {
|
|
90659
90901
|
return namesJob(command, markerDir(marker), platform2);
|
|
@@ -95406,7 +95648,16 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
95406
95648
|
}
|
|
95407
95649
|
const { changes: workflowChanges, states: workflowStates } = await previewWorkflowChanges(deps, result, ours, saved?.calendarPending);
|
|
95408
95650
|
const logins = (typedPlan.users ?? []).filter((u) => result.items.some((i) => i.ref === u.ref && i.type === "users" && i.status === "would_create")).map((u) => ({ name: [u.firstName, u.lastName === "-" ? "" : u.lastName].filter(Boolean).join(" "), email: u.email, role: u.role }));
|
|
95409
|
-
const
|
|
95651
|
+
const otherPlans = createdByOtherPlans(activeLocation, { planHash: contentHash2(typedPlan), planId: typedPlan.planId });
|
|
95652
|
+
const workflowStatus = /* @__PURE__ */ new Map();
|
|
95653
|
+
try {
|
|
95654
|
+
const listed = workflowInventorySchema.safeParse(await client.get("/workflows/", { params: { locationId: activeLocation } }));
|
|
95655
|
+
if (listed.success) {
|
|
95656
|
+
for (const w of listed.data.workflows) if (typeof w.status === "string") workflowStatus.set(w.id, String(w.status));
|
|
95657
|
+
}
|
|
95658
|
+
} catch {
|
|
95659
|
+
}
|
|
95660
|
+
const preview = composePreview({ locationId: activeLocation, recordKey, planHash: contentHash2(typedPlan), items: result.items, ours, workflowChanges, workflowStates, board, logins, otherPlans, workflowStatus, ...bookingCalendarName(typedPlan, result) && { bookingCalendar: bookingCalendarName(typedPlan, result) } });
|
|
95410
95661
|
try {
|
|
95411
95662
|
saveBuildPreview(preview);
|
|
95412
95663
|
} catch (e) {
|
|
@@ -95417,7 +95668,8 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
95417
95668
|
return jsonResponse({
|
|
95418
95669
|
ok: false,
|
|
95419
95670
|
phase: "confirm",
|
|
95420
|
-
error: confirm2 ? BUILD_PREVIEW_CHANGED : BUILD_PAUSED,
|
|
95671
|
+
error: preview.earlierBuild?.length ? earlierBuildText(preview.earlierBuild, locationName ?? "This account").why : confirm2 ? BUILD_PREVIEW_CHANGED : BUILD_PAUSED,
|
|
95672
|
+
...preview.earlierBuild?.length ? { earlierBuild: preview.earlierBuild, undoWithClaude: earlierBuildText(preview.earlierBuild, locationName ?? "This account").undo } : {},
|
|
95421
95673
|
willCreate: preview.createCount,
|
|
95422
95674
|
willUse: preview.reuse.map((e) => `${PREVIEW_KIND[e.type] ?? e.type} "${e.name}"`),
|
|
95423
95675
|
wouldChange: preview.change.map((e) => ({ what: `${PREVIEW_KIND[e.type] ?? e.type} "${e.name}"`, changes: e.changes ?? [] }))
|
|
@@ -95434,7 +95686,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
95434
95686
|
const runId = newRunId();
|
|
95435
95687
|
const journalParents = parentRefIndex(typedPlan);
|
|
95436
95688
|
try {
|
|
95437
|
-
appendBuildStart({ runId, locationId: activeLocation, locationName, planId: typedPlan.planId });
|
|
95689
|
+
appendBuildStart({ runId, locationId: activeLocation, locationName, planId: typedPlan.planId, planHash: contentHash2(typedPlan) });
|
|
95438
95690
|
if (notOurs.length) appendBuildNotOurs({ runId, locationId: activeLocation, objects: notOurs.map((e) => ({ type: e.type, name: e.name, ghlId: e.id })) });
|
|
95439
95691
|
if (allowedChanges.length) appendBuildChanges({ runId, locationId: activeLocation, changes: allowedChanges });
|
|
95440
95692
|
} catch (e) {
|
|
@@ -96821,6 +97073,21 @@ var os11 = __toESM(require("node:os"));
|
|
|
96821
97073
|
function sha2563(buf) {
|
|
96822
97074
|
return (0, import_node_crypto34.createHash)("sha256").update(buf).digest("hex");
|
|
96823
97075
|
}
|
|
97076
|
+
function isLink(p) {
|
|
97077
|
+
try {
|
|
97078
|
+
return fs65.lstatSync(p).isSymbolicLink();
|
|
97079
|
+
} catch {
|
|
97080
|
+
return false;
|
|
97081
|
+
}
|
|
97082
|
+
}
|
|
97083
|
+
function linkOnTheWay(root, rel) {
|
|
97084
|
+
let at = root;
|
|
97085
|
+
for (const part of rel.split(/[\\/]/).filter(Boolean)) {
|
|
97086
|
+
at = path65.join(at, part);
|
|
97087
|
+
if (isLink(at)) return true;
|
|
97088
|
+
}
|
|
97089
|
+
return false;
|
|
97090
|
+
}
|
|
96824
97091
|
function isOlderRelease(candidate, installed) {
|
|
96825
97092
|
const parts = (v) => v.trim().split(".").map((n) => Number.parseInt(n, 10));
|
|
96826
97093
|
const a = parts(candidate), b = parts(installed);
|
|
@@ -96868,6 +97135,7 @@ function installBundledSkills(opts) {
|
|
|
96868
97135
|
updated: [],
|
|
96869
97136
|
skippedUserModified: [],
|
|
96870
97137
|
skippedOlderPackage: [],
|
|
97138
|
+
skippedSymlink: [],
|
|
96871
97139
|
unchanged: [],
|
|
96872
97140
|
errors: [],
|
|
96873
97141
|
targetDir: opts.targetDir ?? defaultSkillsDir(),
|
|
@@ -96882,6 +97150,11 @@ function installBundledSkills(opts) {
|
|
|
96882
97150
|
result.errors.push(`cannot create ${result.targetDir}: ${String(e)}`);
|
|
96883
97151
|
return result;
|
|
96884
97152
|
}
|
|
97153
|
+
if (isLink(result.targetDir)) {
|
|
97154
|
+
result.targetIsLink = true;
|
|
97155
|
+
result.skippedSymlink.push(...listFiles(bundled));
|
|
97156
|
+
return result;
|
|
97157
|
+
}
|
|
96885
97158
|
const markerPath = path65.join(result.targetDir, ".ghl-command-skills.json");
|
|
96886
97159
|
const marker = readMarker(markerPath) ?? { packageVersion: "", files: {} };
|
|
96887
97160
|
const newMarker = { packageVersion: opts.packageVersion, files: { ...marker.files } };
|
|
@@ -96889,6 +97162,10 @@ function installBundledSkills(opts) {
|
|
|
96889
97162
|
try {
|
|
96890
97163
|
const src = fs65.readFileSync(path65.join(bundled, rel));
|
|
96891
97164
|
const dest = path65.join(result.targetDir, rel);
|
|
97165
|
+
if (linkOnTheWay(result.targetDir, rel)) {
|
|
97166
|
+
result.skippedSymlink.push(rel);
|
|
97167
|
+
continue;
|
|
97168
|
+
}
|
|
96892
97169
|
const srcHash = sha2563(src);
|
|
96893
97170
|
let destBuf = null;
|
|
96894
97171
|
try {
|
|
@@ -96926,6 +97203,7 @@ function installBundledSkills(opts) {
|
|
|
96926
97203
|
}
|
|
96927
97204
|
}
|
|
96928
97205
|
try {
|
|
97206
|
+
if (isLink(markerPath)) throw new Error("the marker file is a symbolic link; not written");
|
|
96929
97207
|
fs65.writeFileSync(markerPath, JSON.stringify(newMarker, null, 2));
|
|
96930
97208
|
} catch (e) {
|
|
96931
97209
|
result.errors.push(`marker write failed: ${String(e)}`);
|
|
@@ -96946,6 +97224,7 @@ function summarizeInstall(r) {
|
|
|
96946
97224
|
bucket(r.updated, "updated");
|
|
96947
97225
|
bucket(r.skippedUserModified, "kept with your edits");
|
|
96948
97226
|
bucket(r.skippedOlderPackage, "kept, newer than this version");
|
|
97227
|
+
bucket(r.skippedSymlink ?? [], "not touched, linked elsewhere");
|
|
96949
97228
|
if (r.errors.length) bits.push(`${r.errors.length} file error${r.errors.length === 1 ? "" : "s"}`);
|
|
96950
97229
|
if (bits.length === 0) return "";
|
|
96951
97230
|
return `Skills: ${bits.join(", ")} \u2192 ${r.targetDir}`;
|
|
@@ -96973,7 +97252,7 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
|
|
|
96973
97252
|
ok: result.errors.length === 0,
|
|
96974
97253
|
targetDir: result.targetDir,
|
|
96975
97254
|
// Skills, by name, before the per-file detail (BUG-2026-09-14-03).
|
|
96976
|
-
skills: { installed: skillNames(result.installed), updated: skillNames(result.updated), keptYourEdits: skillNames(result.skippedUserModified) },
|
|
97255
|
+
skills: { installed: skillNames(result.installed), updated: skillNames(result.updated), keptYourEdits: skillNames(result.skippedUserModified), linkedElsewhere: skillNames(result.skippedSymlink) },
|
|
96977
97256
|
installed: result.installed,
|
|
96978
97257
|
updated: result.updated,
|
|
96979
97258
|
keptYourEdits: result.skippedUserModified,
|
|
@@ -96982,7 +97261,14 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
|
|
|
96982
97261
|
keptNewerThanThisVersion: result.skippedOlderPackage,
|
|
96983
97262
|
unchanged: result.unchanged.length,
|
|
96984
97263
|
errors: result.errors,
|
|
96985
|
-
|
|
97264
|
+
// Reviewer MED-2 on rc.1: a skills folder (or skill folder) that is a link elsewhere is never written, and the
|
|
97265
|
+
// reply says so instead of "Everything already current."
|
|
97266
|
+
linkedElsewhere: result.skippedSymlink,
|
|
97267
|
+
next: [
|
|
97268
|
+
result.installed.length > 0 || result.updated.length > 0 ? "Fully restart Claude (quit and reopen) so the new/updated skills load." : "",
|
|
97269
|
+
result.skippedSymlink.length ? `Not installed: ${skillNames(result.skippedSymlink).join(", ")}, because ${linkedWhere(result)} is a link to somewhere else, and GHL Command never writes through a link. Replace the link with a real folder to get these skills.` : "",
|
|
97270
|
+
result.installed.length === 0 && result.updated.length === 0 && result.skippedSymlink.length === 0 ? "Everything already current." : ""
|
|
97271
|
+
].filter(Boolean).join(" ")
|
|
96986
97272
|
});
|
|
96987
97273
|
} catch (error) {
|
|
96988
97274
|
return errorResponse(error);
|
|
@@ -96990,6 +97276,10 @@ function registerSkillsTool(server2, packageVersion, baseDir) {
|
|
|
96990
97276
|
}
|
|
96991
97277
|
);
|
|
96992
97278
|
}
|
|
97279
|
+
function linkedWhere(r) {
|
|
97280
|
+
if (r.targetIsLink) return `the skills folder (${r.targetDir})`;
|
|
97281
|
+
return `the folder for ${skillNames(r.skippedSymlink).length === 1 ? "that skill" : "each of those skills"}`;
|
|
97282
|
+
}
|
|
96993
97283
|
|
|
96994
97284
|
// src/index.ts
|
|
96995
97285
|
init_brain();
|