@elitedcs/ghl-mcp 3.84.6 → 3.84.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/index.js +1277 -717
- package/guide/guide.html +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -127,7 +127,7 @@ var init_account_read_retry = __esm({
|
|
|
127
127
|
inBuildRequestScope = () => buildScope.getStore() !== void 0;
|
|
128
128
|
inAccountReadAttempt = () => readScope.getStore() === true;
|
|
129
129
|
withBuildRequestScope = (run, wait = sleep) => buildScope.run({ wait }, run);
|
|
130
|
-
sleep = (ms) => new Promise((
|
|
130
|
+
sleep = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -3436,8 +3436,6 @@ var init_tool_filter = __esm({
|
|
|
3436
3436
|
"attach_assessment",
|
|
3437
3437
|
// source GETs plus local card and intake drafts only
|
|
3438
3438
|
"set_income_source",
|
|
3439
|
-
"record_brief_answers",
|
|
3440
|
-
// local chat rows only; no account/provider writes
|
|
3441
3439
|
"connect_system",
|
|
3442
3440
|
"disconnect_system"
|
|
3443
3441
|
]);
|
|
@@ -5474,12 +5472,12 @@ function registerFormTools(server2, client) {
|
|
|
5474
5472
|
startAt: import_zod16.z.string().optional().describe("Start date filter (ISO string)"),
|
|
5475
5473
|
endAt: import_zod16.z.string().optional().describe("End date filter (ISO string)")
|
|
5476
5474
|
},
|
|
5477
|
-
async ({ locationId: locationId2, page, limit, formId, q:
|
|
5475
|
+
async ({ locationId: locationId2, page, limit, formId, q: q3, startAt, endAt }) => {
|
|
5478
5476
|
const resolvedLocationId = client.resolveLocationId(locationId2);
|
|
5479
5477
|
const params = { locationId: resolvedLocationId, limit: limit ?? 20 };
|
|
5480
5478
|
if (page !== void 0) params.page = page;
|
|
5481
5479
|
if (formId !== void 0) params.formId = formId;
|
|
5482
|
-
if (
|
|
5480
|
+
if (q3 !== void 0) params.q = q3;
|
|
5483
5481
|
if (startAt !== void 0) params.startAt = startAt;
|
|
5484
5482
|
if (endAt !== void 0) params.endAt = endAt;
|
|
5485
5483
|
return client.get("/forms/submissions", { params });
|
|
@@ -5528,13 +5526,13 @@ function registerSurveyTools(server2, client) {
|
|
|
5528
5526
|
startAt: import_zod17.z.string().optional().describe("Start date filter (ISO string)"),
|
|
5529
5527
|
endAt: import_zod17.z.string().optional().describe("End date filter (ISO string)")
|
|
5530
5528
|
},
|
|
5531
|
-
async ({ locationId: locationId2, page, limit, surveyId, q:
|
|
5529
|
+
async ({ locationId: locationId2, page, limit, surveyId, q: q3, startAt, endAt }) => {
|
|
5532
5530
|
const resolvedLocationId = client.resolveLocationId(locationId2);
|
|
5533
5531
|
const params = { locationId: resolvedLocationId };
|
|
5534
5532
|
if (page !== void 0) params.page = page;
|
|
5535
5533
|
if (limit !== void 0) params.limit = limit;
|
|
5536
5534
|
if (surveyId !== void 0) params.surveyId = surveyId;
|
|
5537
|
-
if (
|
|
5535
|
+
if (q3 !== void 0) params.q = q3;
|
|
5538
5536
|
if (startAt !== void 0) params.startAt = startAt;
|
|
5539
5537
|
if (endAt !== void 0) params.endAt = endAt;
|
|
5540
5538
|
return client.get("/surveys/submissions", { params });
|
|
@@ -6194,12 +6192,12 @@ function buildCreateUserBody(args, opts = {}) {
|
|
|
6194
6192
|
function generateTemporaryPassword() {
|
|
6195
6193
|
const upper2 = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
6196
6194
|
const lower = "abcdefghijkmnopqrstuvwxyz";
|
|
6197
|
-
const
|
|
6195
|
+
const digits = "23456789";
|
|
6198
6196
|
const symbols = "!@#$%&*?";
|
|
6199
|
-
const all = upper2 + lower +
|
|
6197
|
+
const all = upper2 + lower + digits + symbols;
|
|
6200
6198
|
const bytes = (0, import_node_crypto.randomBytes)(24);
|
|
6201
6199
|
const pick2 = (set, b) => set[b % set.length];
|
|
6202
|
-
const chars = [pick2(upper2, bytes[0]), pick2(lower, bytes[1]), pick2(
|
|
6200
|
+
const chars = [pick2(upper2, bytes[0]), pick2(lower, bytes[1]), pick2(digits, bytes[2]), pick2(symbols, bytes[3])];
|
|
6203
6201
|
for (let i = 4; i < 20; i++) chars.push(pick2(all, bytes[i]));
|
|
6204
6202
|
for (let i = chars.length - 1; i > 0; i--) {
|
|
6205
6203
|
const j = bytes[(i + 20) % bytes.length] % (i + 1);
|
|
@@ -8332,18 +8330,18 @@ async function readAgent(client, agentId) {
|
|
|
8332
8330
|
return a;
|
|
8333
8331
|
}
|
|
8334
8332
|
async function updateAgentFields(client, agentId, change) {
|
|
8335
|
-
const
|
|
8336
|
-
for (const [k, val] of Object.entries(change)) if (val !== void 0)
|
|
8337
|
-
if (Object.keys(
|
|
8333
|
+
const clean2 = {};
|
|
8334
|
+
for (const [k, val] of Object.entries(change)) if (val !== void 0) clean2[k] = val;
|
|
8335
|
+
if (Object.keys(clean2).length === 0) throw new Error("Nothing to update \u2014 pass at least one field to change.");
|
|
8338
8336
|
const before = await readAgent(client, agentId);
|
|
8339
|
-
const body = { ...promptTrio(before), ...
|
|
8337
|
+
const body = { ...promptTrio(before), ...clean2 };
|
|
8340
8338
|
const merged = promptTrio(body);
|
|
8341
8339
|
if (!merged.personality || !merged.goal || !merged.instructions) {
|
|
8342
8340
|
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).`);
|
|
8343
8341
|
}
|
|
8344
8342
|
await client.put(`/conversation-ai/agents/${agentId}`, { version: AI_API_VERSION, body });
|
|
8345
8343
|
const after = await readAgent(client, agentId);
|
|
8346
|
-
return { before, changed: Object.keys(
|
|
8344
|
+
return { before, changed: Object.keys(clean2), after };
|
|
8347
8345
|
}
|
|
8348
8346
|
function registerConversationAiTools(server2, client) {
|
|
8349
8347
|
const v = { version: AI_API_VERSION };
|
|
@@ -8980,7 +8978,7 @@ function registerAiEmployeeDemoTools(server2, shared, deps = {}) {
|
|
|
8980
8978
|
const utc = (s) => /^\d{4}-\d{2}-\d{2}T[\d:.]+$/.test(s) ? `${s}Z` : s;
|
|
8981
8979
|
const when = (m) => Date.parse(utc(String(m.dateAdded ?? m.createdAt ?? m.dateUpdated ?? ""))) || 0;
|
|
8982
8980
|
const start = Date.parse(run.startedAt) || 0;
|
|
8983
|
-
const
|
|
8981
|
+
const stop2 = run.endedAt ? Date.parse(run.endedAt) || Infinity : Infinity;
|
|
8984
8982
|
const SKEW_MS = 15e3;
|
|
8985
8983
|
let beforeStart = 0;
|
|
8986
8984
|
let afterEnd = 0;
|
|
@@ -8995,7 +8993,7 @@ function registerAiEmployeeDemoTools(server2, shared, deps = {}) {
|
|
|
8995
8993
|
beforeStart++;
|
|
8996
8994
|
return false;
|
|
8997
8995
|
}
|
|
8998
|
-
if (t >
|
|
8996
|
+
if (t > stop2 + SKEW_MS) {
|
|
8999
8997
|
afterEnd++;
|
|
9000
8998
|
return false;
|
|
9001
8999
|
}
|
|
@@ -11616,15 +11614,15 @@ ${text2.slice(0, 300)}`);
|
|
|
11616
11614
|
}
|
|
11617
11615
|
return text2 ? JSON.parse(text2) : {};
|
|
11618
11616
|
}
|
|
11619
|
-
async function searchTemplates(api,
|
|
11620
|
-
if (
|
|
11621
|
-
const wanted = new Set(
|
|
11622
|
-
const limit = Math.min(Math.max(
|
|
11623
|
-
const offset = Math.max(
|
|
11617
|
+
async function searchTemplates(api, q3 = {}) {
|
|
11618
|
+
if (q3.categories?.length) {
|
|
11619
|
+
const wanted = new Set(q3.categories.map((name) => name.trim().toLowerCase()));
|
|
11620
|
+
const limit = Math.min(Math.max(q3.limit ?? 24, 1), 200);
|
|
11621
|
+
const offset = Math.max(q3.offset ?? 0, 0);
|
|
11624
11622
|
const matches = [];
|
|
11625
11623
|
const seen = /* @__PURE__ */ new Set();
|
|
11626
11624
|
for (let upstreamOffset = 0; ; upstreamOffset += 200) {
|
|
11627
|
-
const page = await searchTemplates(api, { ...
|
|
11625
|
+
const page = await searchTemplates(api, { ...q3, categories: [], limit: 200, offset: upstreamOffset });
|
|
11628
11626
|
const fresh = page.filter((row) => !seen.has(row._id));
|
|
11629
11627
|
if (page.length && !fresh.length) throw new Error("GoHighLevel repeated a template page. The category search could not finish.");
|
|
11630
11628
|
for (const row of fresh) {
|
|
@@ -11639,19 +11637,19 @@ async function searchTemplates(api, q2 = {}) {
|
|
|
11639
11637
|
const body = {
|
|
11640
11638
|
companyId: requireCompany(api),
|
|
11641
11639
|
locationId: api.locationId(),
|
|
11642
|
-
product:
|
|
11643
|
-
keyword:
|
|
11640
|
+
product: q3.product ?? "funnels",
|
|
11641
|
+
keyword: q3.keyword ?? "",
|
|
11644
11642
|
type: "all",
|
|
11645
|
-
categories:
|
|
11646
|
-
features:
|
|
11647
|
-
tags:
|
|
11648
|
-
colors:
|
|
11643
|
+
categories: q3.categories ?? [],
|
|
11644
|
+
features: q3.features ?? [],
|
|
11645
|
+
tags: q3.tags ?? [],
|
|
11646
|
+
colors: q3.colors ?? [],
|
|
11649
11647
|
status: [],
|
|
11650
11648
|
createdBy: [],
|
|
11651
11649
|
isAdminView: false,
|
|
11652
11650
|
system: false,
|
|
11653
|
-
limit: Math.min(Math.max(
|
|
11654
|
-
offset:
|
|
11651
|
+
limit: Math.min(Math.max(q3.limit ?? 24, 1), 200),
|
|
11652
|
+
offset: q3.offset ?? 0
|
|
11655
11653
|
};
|
|
11656
11654
|
const out = await call(api, "/templates/list", { method: "POST", body: JSON.stringify(body) });
|
|
11657
11655
|
return Array.isArray(out) ? out : out.templates ?? [];
|
|
@@ -12022,8 +12020,8 @@ function repaintReport(before, after) {
|
|
|
12022
12020
|
const coloured = (c) => c.startsWith("#") && hexToOklch(c).c >= CARRIES_COLOUR;
|
|
12023
12021
|
for (const p of a.paints) {
|
|
12024
12022
|
if (!p.colour || p.colour === "transparent") continue;
|
|
12025
|
-
const
|
|
12026
|
-
const to =
|
|
12023
|
+
const q3 = afterByPath.get(p.path);
|
|
12024
|
+
const to = q3?.colour ?? null;
|
|
12027
12025
|
const differs = to !== null && looksDifferent(p.colour, to);
|
|
12028
12026
|
if (p.kind === "background" || p.kind === "text") {
|
|
12029
12027
|
const t = p.kind === "text" ? tally.text : coloured(p.colour) ? tally.background : tally.neutral;
|
|
@@ -15702,12 +15700,12 @@ function deriveBrandDna(raw, opts = {}) {
|
|
|
15702
15700
|
fixed = true;
|
|
15703
15701
|
}
|
|
15704
15702
|
const vb = svg.match(/viewBox=["']\s*[\d.-]+\s+[\d.-]+\s+([\d.]+)\s+([\d.]+)/i);
|
|
15705
|
-
const
|
|
15706
|
-
if (
|
|
15707
|
-
logoSvg =
|
|
15708
|
-
logo = { inlineSvg:
|
|
15703
|
+
const clean2 = sanitizeSvgOrDrop(svg);
|
|
15704
|
+
if (clean2) {
|
|
15705
|
+
logoSvg = clean2;
|
|
15706
|
+
logo = { inlineSvg: clean2, whiteFillFixed: fixed, source: bl.where, rights: opts.rights ? "declared" : "inherits", ...vb && { width: parseFloat(vb[1]), height: parseFloat(vb[2]) } };
|
|
15709
15707
|
declared++;
|
|
15710
|
-
evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${
|
|
15708
|
+
evidence["logo"] = `${bl.where}${fixed ? "; white fills replaced with currentColor" : ""}${clean2 !== svg ? "; scripts, handlers and outside references stripped" : ""}`;
|
|
15711
15709
|
} else {
|
|
15712
15710
|
evidence["logo"] = `${bl.where}: not a plain drawing (or over 60 KB), so it was not kept`;
|
|
15713
15711
|
}
|
|
@@ -16654,9 +16652,9 @@ function removalHoldBack(opts) {
|
|
|
16654
16652
|
return null;
|
|
16655
16653
|
}
|
|
16656
16654
|
function brandedPath(path42, name, taken) {
|
|
16657
|
-
const
|
|
16658
|
-
const base =
|
|
16659
|
-
const used = new Set([...taken].map(
|
|
16655
|
+
const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
16656
|
+
const base = normalize2(path42 ?? "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
|
|
16657
|
+
const used = new Set([...taken].map(normalize2));
|
|
16660
16658
|
let candidate = `${base}-branded`;
|
|
16661
16659
|
for (let n = 2; used.has(candidate); n++) candidate = `${base}-branded-${n}`;
|
|
16662
16660
|
taken.add(`/${candidate}`);
|
|
@@ -16665,7 +16663,7 @@ function brandedPath(path42, name, taken) {
|
|
|
16665
16663
|
function rewriteTemplateLinks(envelope, destinations) {
|
|
16666
16664
|
const links = [];
|
|
16667
16665
|
const unmapped = [];
|
|
16668
|
-
const
|
|
16666
|
+
const normalize2 = (s) => s.replace(/^\/+|\/+$/g, "");
|
|
16669
16667
|
function rewrite(from, kind) {
|
|
16670
16668
|
if (!from) return from;
|
|
16671
16669
|
let matches = [];
|
|
@@ -16685,7 +16683,7 @@ function rewriteTemplateLinks(envelope, destinations) {
|
|
|
16685
16683
|
} else if (!/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(from) && !from.startsWith("#")) {
|
|
16686
16684
|
const [path42] = from.split(/[?#]/);
|
|
16687
16685
|
suffix = from.slice(path42.length);
|
|
16688
|
-
matches = destinations.filter((d2) => d2.path &&
|
|
16686
|
+
matches = destinations.filter((d2) => d2.path && normalize2(d2.path) === normalize2(path42));
|
|
16689
16687
|
}
|
|
16690
16688
|
}
|
|
16691
16689
|
if (matches.length !== 1) {
|
|
@@ -16697,7 +16695,7 @@ function rewriteTemplateLinks(envelope, destinations) {
|
|
|
16697
16695
|
unmapped.push({ from, reason: "the target step was not rebuilt" });
|
|
16698
16696
|
return from;
|
|
16699
16697
|
}
|
|
16700
|
-
const to = representation === "step" ? d.newStepId : representation === "page" ? `${previewPrefix}${d.newPageId}${suffix}` : `${from.startsWith("/") ? d.newPath :
|
|
16698
|
+
const to = representation === "step" ? d.newStepId : representation === "page" ? `${previewPrefix}${d.newPageId}${suffix}` : `${from.startsWith("/") ? d.newPath : normalize2(d.newPath)}${suffix}`;
|
|
16701
16699
|
links.push({ from, to });
|
|
16702
16700
|
return to;
|
|
16703
16701
|
}
|
|
@@ -17285,7 +17283,7 @@ ${await response.text()}`);
|
|
|
17285
17283
|
}
|
|
17286
17284
|
const confirmed = /* @__PURE__ */ new Set();
|
|
17287
17285
|
for (let attempt = 0; accepted.length && attempt < 5; attempt++) {
|
|
17288
|
-
if (attempt) await new Promise((
|
|
17286
|
+
if (attempt) await new Promise((resolve13) => setTimeout(resolve13, attempt * 1e3));
|
|
17289
17287
|
try {
|
|
17290
17288
|
const listed = await funnelRequest("GET", `/funnel/list?locationId=${client.locationId}&limit=${FUNNEL_PAGE}`);
|
|
17291
17289
|
const current = listed.funnels?.find((item) => item._id === funnel._id);
|
|
@@ -18076,7 +18074,7 @@ function registerFunnelQaTools(server2, client, builderClient) {
|
|
|
18076
18074
|
last_name: contact.lastName,
|
|
18077
18075
|
name: contact.name
|
|
18078
18076
|
};
|
|
18079
|
-
const
|
|
18077
|
+
const same2 = (key, a, b) => key === "phone" ? (a ?? "").replace(/\D/g, "") === (b ?? "").replace(/\D/g, "") : (a ?? "").trim().toLowerCase() === (b ?? "").trim().toLowerCase();
|
|
18080
18078
|
for (const k of args.expectStandard ?? []) {
|
|
18081
18079
|
const key = k.toLowerCase();
|
|
18082
18080
|
if (!(key in got)) {
|
|
@@ -18089,7 +18087,7 @@ function registerFunnelQaTools(server2, client, builderClient) {
|
|
|
18089
18087
|
continue;
|
|
18090
18088
|
}
|
|
18091
18089
|
if (!got[key]) probs.push(`standard ${k}: not saved`);
|
|
18092
|
-
else if (!
|
|
18090
|
+
else if (!same2(key, got[key], exp)) probs.push(`standard ${k}: saved "${got[key]}" \u2260 sent "${exp}"`);
|
|
18093
18091
|
}
|
|
18094
18092
|
const okCount = (args.expectCustom?.length ?? 0) + (args.expectStandard?.length ?? 0) - probs.length - notes.length;
|
|
18095
18093
|
checks.push(probs.length ? { id: "field_fidelity", label: "Field-value persistence", status: "fail", detail: [probs.join("; "), notes.join("; ")].filter(Boolean).join(" | ") } : { id: "field_fidelity", label: "Field-value persistence", status: "pass", detail: `${okCount} asserted field value(s) persisted with the exact submitted value.${notes.length ? " " + notes.join("; ") : ""}` });
|
|
@@ -18550,6 +18548,22 @@ var init_features = __esm({
|
|
|
18550
18548
|
});
|
|
18551
18549
|
|
|
18552
18550
|
// src/agency-profile.ts
|
|
18551
|
+
var agency_profile_exports = {};
|
|
18552
|
+
__export(agency_profile_exports, {
|
|
18553
|
+
AGENCY_PROFILE_ENV: () => AGENCY_PROFILE_ENV,
|
|
18554
|
+
agencyProfilePath: () => agencyProfilePath,
|
|
18555
|
+
demoRailProfile: () => demoRailProfile,
|
|
18556
|
+
mergeAgencyProfile: () => mergeAgencyProfile,
|
|
18557
|
+
normalizeAccent: () => normalizeAccent,
|
|
18558
|
+
profileIsUsable: () => profileIsUsable,
|
|
18559
|
+
readAgencyProfile: () => readAgencyProfile,
|
|
18560
|
+
safeDir: () => safeDir,
|
|
18561
|
+
safeUrl: () => safeUrl2,
|
|
18562
|
+
sanitizeAgencyOs: () => sanitizeAgencyOs,
|
|
18563
|
+
sanitizeProfile: () => sanitizeProfile,
|
|
18564
|
+
saveAgencyProfileAnswer: () => saveAgencyProfileAnswer,
|
|
18565
|
+
writeAgencyProfile: () => writeAgencyProfile
|
|
18566
|
+
});
|
|
18553
18567
|
function agencyProfilePath() {
|
|
18554
18568
|
const override = process.env[AGENCY_PROFILE_ENV];
|
|
18555
18569
|
return override && override.trim() ? override : path8.join(appDataDir(), "agency-profile.json");
|
|
@@ -18574,6 +18588,11 @@ function safeUrl2(x, max = MAX_URL) {
|
|
|
18574
18588
|
if (u.protocol === "http:" && /^(localhost|127\.0\.0\.1|\[::1\])$/.test(u.hostname)) return raw;
|
|
18575
18589
|
return void 0;
|
|
18576
18590
|
}
|
|
18591
|
+
function safeDir(x) {
|
|
18592
|
+
const raw = typeof x === "string" ? x.trim() : "";
|
|
18593
|
+
if (!raw || raw.length > 1024 || raw.includes("\0") || !path8.isAbsolute(raw)) return void 0;
|
|
18594
|
+
return path8.normalize(raw);
|
|
18595
|
+
}
|
|
18577
18596
|
function normalizeAccent(x) {
|
|
18578
18597
|
const s = typeof x === "string" ? x.trim() : "";
|
|
18579
18598
|
return /^#[0-9a-fA-F]{6}$/.test(s) ? s.toLowerCase() : void 0;
|
|
@@ -18648,6 +18667,7 @@ function sanitizeProfile(raw) {
|
|
|
18648
18667
|
industry: str2(defaults?.industry, 64)
|
|
18649
18668
|
}),
|
|
18650
18669
|
agencyOs: sanitizeAgencyOs(r.agencyOs),
|
|
18670
|
+
clientFilesDir: safeDir(r.clientFilesDir),
|
|
18651
18671
|
updated_at: str2(r.updated_at, 40)
|
|
18652
18672
|
};
|
|
18653
18673
|
}
|
|
@@ -18792,6 +18812,17 @@ function writeAgencyProfile(patch, file2 = agencyProfilePath()) {
|
|
|
18792
18812
|
function profileIsUsable(p) {
|
|
18793
18813
|
return Boolean(p.agencyName);
|
|
18794
18814
|
}
|
|
18815
|
+
function demoRailProfile(p = readAgencyProfile(), ghl, demoDays = 7) {
|
|
18816
|
+
if (!profileIsUsable(p)) return void 0;
|
|
18817
|
+
const rec = { agencyName: p.agencyName };
|
|
18818
|
+
if (p.accent) rec.accent = p.accent;
|
|
18819
|
+
if (p.logoUrl) rec.logoUrl = p.logoUrl;
|
|
18820
|
+
if (p.sender) rec.senderIdentity = p.sender;
|
|
18821
|
+
const g = drop({ locationId: str2(ghl?.locationId), triggerTag: str2(ghl?.triggerTag, 60) });
|
|
18822
|
+
if (g) rec.ghl = g;
|
|
18823
|
+
rec.demoDays = num(demoDays) ?? 7;
|
|
18824
|
+
return rec;
|
|
18825
|
+
}
|
|
18795
18826
|
function sanitizePlanEvidence(raw) {
|
|
18796
18827
|
if (!raw || typeof raw !== "object") return void 0;
|
|
18797
18828
|
const r = raw;
|
|
@@ -18841,7 +18872,8 @@ var init_agency_profile = __esm({
|
|
|
18841
18872
|
"defaults",
|
|
18842
18873
|
"agencyOs",
|
|
18843
18874
|
"canCreateSubAccounts",
|
|
18844
|
-
"ghlPlanEvidence"
|
|
18875
|
+
"ghlPlanEvidence",
|
|
18876
|
+
"clientFilesDir"
|
|
18845
18877
|
];
|
|
18846
18878
|
NESTED = /* @__PURE__ */ new Set(["sender", "pricing", "delivery", "defaults", "agencyOs"]);
|
|
18847
18879
|
isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -20006,56 +20038,17 @@ var init_brief_deadline = __esm({
|
|
|
20006
20038
|
}
|
|
20007
20039
|
});
|
|
20008
20040
|
|
|
20009
|
-
// src/command-os/connectors/brief-
|
|
20010
|
-
|
|
20011
|
-
|
|
20012
|
-
|
|
20013
|
-
} catch (error) {
|
|
20014
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
20015
|
-
throw new Error("The saved chat brief could not be read; nothing was replaced.");
|
|
20016
|
-
}
|
|
20017
|
-
}
|
|
20018
|
-
function recordBriefAnswers(input) {
|
|
20019
|
-
const { source, readAt, rows: rows2 } = BriefAnswersInput.parse(input);
|
|
20020
|
-
const records = readBriefAnswers();
|
|
20021
|
-
records[source] = { readAt, rows: rows2 };
|
|
20022
|
-
ensureAppDataDir();
|
|
20023
|
-
const tmp = `${briefAnswersPath()}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
20024
|
-
try {
|
|
20025
|
-
fs11.writeFileSync(tmp, JSON.stringify(records) + "\n", { mode: 384, flag: "wx" });
|
|
20026
|
-
fs11.renameSync(tmp, briefAnswersPath());
|
|
20027
|
-
} catch {
|
|
20028
|
-
throw new Error("The chat brief could not be saved privately on this machine.");
|
|
20029
|
-
} finally {
|
|
20030
|
-
try {
|
|
20031
|
-
fs11.unlinkSync(tmp);
|
|
20032
|
-
} catch {
|
|
20033
|
-
}
|
|
20034
|
-
}
|
|
20035
|
-
return { source, rows: rows2.length, readAt };
|
|
20036
|
-
}
|
|
20037
|
-
var fs11, path10, import_node_crypto4, import_zod49, BriefRowSchema, BriefAnswersInput, RecordSchema, FileSchema, briefAnswersPath;
|
|
20038
|
-
var init_brief_answers = __esm({
|
|
20039
|
-
"src/command-os/connectors/brief-answers.ts"() {
|
|
20041
|
+
// src/command-os/connectors/brief-row.ts
|
|
20042
|
+
var import_zod49, BriefRowSchema;
|
|
20043
|
+
var init_brief_row = __esm({
|
|
20044
|
+
"src/command-os/connectors/brief-row.ts"() {
|
|
20040
20045
|
"use strict";
|
|
20041
|
-
fs11 = __toESM(require("node:fs"));
|
|
20042
|
-
path10 = __toESM(require("node:path"));
|
|
20043
|
-
import_node_crypto4 = require("node:crypto");
|
|
20044
20046
|
import_zod49 = require("zod");
|
|
20045
|
-
init_credentials_store();
|
|
20046
20047
|
BriefRowSchema = import_zod49.z.object({
|
|
20047
20048
|
text: import_zod49.z.string().min(1).max(12e3),
|
|
20048
20049
|
when: import_zod49.z.string().max(300).optional(),
|
|
20049
20050
|
link: import_zod49.z.url().refine((value) => ["https:", "http:"].includes(new URL(value).protocol), "Use an HTTP or HTTPS link.").optional()
|
|
20050
20051
|
}).strict();
|
|
20051
|
-
BriefAnswersInput = import_zod49.z.object({
|
|
20052
|
-
source: import_zod49.z.enum(["gmail", "google-drive"]),
|
|
20053
|
-
readAt: import_zod49.z.iso.datetime({ offset: true }),
|
|
20054
|
-
rows: import_zod49.z.array(BriefRowSchema).max(50, "Record at most 50 rows per source.")
|
|
20055
|
-
}).strict();
|
|
20056
|
-
RecordSchema = BriefAnswersInput.omit({ source: true });
|
|
20057
|
-
FileSchema = import_zod49.z.object({ gmail: RecordSchema.optional(), "google-drive": RecordSchema.optional() }).strict();
|
|
20058
|
-
briefAnswersPath = () => path10.join(appDataDir(), "brief-answers.json");
|
|
20059
20052
|
}
|
|
20060
20053
|
});
|
|
20061
20054
|
|
|
@@ -20156,7 +20149,6 @@ var init_registry = __esm({
|
|
|
20156
20149
|
revoke: "Disconnect Google Drive in your Claude's connector settings.",
|
|
20157
20150
|
tools: {}
|
|
20158
20151
|
},
|
|
20159
|
-
{ id: "meta-ads", name: "Meta Ads", builtState: "not-built", whatItIs: "", checkedOn: null, gives: "Not built yet", needs: [], steps: [], say: [], revoke: "", tools: {} },
|
|
20160
20152
|
{ id: "zoom", name: "Zoom", builtState: "not-built", whatItIs: "", checkedOn: null, gives: "Not built yet", needs: [], steps: [], say: [], revoke: "", tools: {} },
|
|
20161
20153
|
{ id: "plaud", name: "Plaud", builtState: "not-built", whatItIs: "", checkedOn: null, gives: "Not built yet", needs: [], steps: [], say: [], revoke: "", tools: {} }
|
|
20162
20154
|
];
|
|
@@ -20165,11 +20157,11 @@ var init_registry = __esm({
|
|
|
20165
20157
|
|
|
20166
20158
|
// src/command-os/connectors/store.ts
|
|
20167
20159
|
function connectorsPath() {
|
|
20168
|
-
return
|
|
20160
|
+
return path10.join(appDataDir(), "connectors.json");
|
|
20169
20161
|
}
|
|
20170
20162
|
function readFile() {
|
|
20171
20163
|
try {
|
|
20172
|
-
const raw =
|
|
20164
|
+
const raw = fs11.readFileSync(connectorsPath(), "utf-8");
|
|
20173
20165
|
const parsed2 = JSON.parse(raw);
|
|
20174
20166
|
if (!parsed2 || typeof parsed2 !== "object" || !parsed2.connectors) return { v: 1, connectors: {} };
|
|
20175
20167
|
return parsed2;
|
|
@@ -20181,7 +20173,7 @@ function readForWrite() {
|
|
|
20181
20173
|
const target = connectorsPath();
|
|
20182
20174
|
let raw;
|
|
20183
20175
|
try {
|
|
20184
|
-
raw =
|
|
20176
|
+
raw = fs11.readFileSync(target, "utf-8");
|
|
20185
20177
|
} catch (e) {
|
|
20186
20178
|
if (e?.code === "ENOENT") return { v: 1, connectors: {} };
|
|
20187
20179
|
throw new Error("Your connector file could not be read, so nothing was changed. Nothing was lost.");
|
|
@@ -20193,8 +20185,8 @@ function readForWrite() {
|
|
|
20193
20185
|
} catch {
|
|
20194
20186
|
const kept = `${target}.unreadable-${Date.now()}`;
|
|
20195
20187
|
try {
|
|
20196
|
-
|
|
20197
|
-
|
|
20188
|
+
fs11.copyFileSync(target, kept);
|
|
20189
|
+
fs11.chmodSync(kept, 384);
|
|
20198
20190
|
} catch {
|
|
20199
20191
|
}
|
|
20200
20192
|
throw new Error(
|
|
@@ -20208,31 +20200,36 @@ function writeFile(f) {
|
|
|
20208
20200
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
20209
20201
|
let renamed = false;
|
|
20210
20202
|
try {
|
|
20211
|
-
|
|
20203
|
+
fs11.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
20212
20204
|
try {
|
|
20213
|
-
|
|
20205
|
+
fs11.chmodSync(tmp, 384);
|
|
20214
20206
|
} catch {
|
|
20215
20207
|
}
|
|
20216
|
-
|
|
20208
|
+
fs11.renameSync(tmp, target);
|
|
20217
20209
|
renamed = true;
|
|
20218
20210
|
} finally {
|
|
20219
20211
|
if (!renamed) {
|
|
20220
20212
|
try {
|
|
20221
|
-
|
|
20213
|
+
fs11.unlinkSync(tmp);
|
|
20222
20214
|
} catch {
|
|
20223
20215
|
}
|
|
20224
20216
|
}
|
|
20225
20217
|
}
|
|
20226
20218
|
try {
|
|
20227
|
-
|
|
20219
|
+
fs11.chmodSync(target, 384);
|
|
20228
20220
|
} catch {
|
|
20229
20221
|
}
|
|
20230
20222
|
}
|
|
20231
20223
|
function safeLabel(label, key) {
|
|
20232
|
-
|
|
20233
|
-
|
|
20234
|
-
const
|
|
20235
|
-
|
|
20224
|
+
if (label === void 0) return label;
|
|
20225
|
+
let out = label;
|
|
20226
|
+
for (const value of [.../* @__PURE__ */ new Set([key, key.trim()])].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
20227
|
+
for (let length = value.length; length >= 8; length--) {
|
|
20228
|
+
const part = value.slice(0, length);
|
|
20229
|
+
if (out.includes(part)) out = out.split(part).join("[key removed from label]");
|
|
20230
|
+
}
|
|
20231
|
+
}
|
|
20232
|
+
return out;
|
|
20236
20233
|
}
|
|
20237
20234
|
function connect(system, key, label, now = () => /* @__PURE__ */ new Date(), verified = true) {
|
|
20238
20235
|
const trimmed = String(key ?? "").trim();
|
|
@@ -20255,7 +20252,7 @@ function summaryOf(s) {
|
|
|
20255
20252
|
}
|
|
20256
20253
|
function listConnectors(options = {}) {
|
|
20257
20254
|
try {
|
|
20258
|
-
const file2 = import_zod50.z.object({ v: import_zod50.z.literal(1), connectors: import_zod50.z.record(import_zod50.z.string(), import_zod50.z.unknown()) }).parse(JSON.parse(
|
|
20255
|
+
const file2 = import_zod50.z.object({ v: import_zod50.z.literal(1), connectors: import_zod50.z.record(import_zod50.z.string(), import_zod50.z.unknown()) }).parse(JSON.parse(fs11.readFileSync(connectorsPath(), "utf8")));
|
|
20259
20256
|
const summaries = [];
|
|
20260
20257
|
for (const [system, value] of Object.entries(file2.connectors)) {
|
|
20261
20258
|
if (!SystemSchema.safeParse(system).success) continue;
|
|
@@ -20281,16 +20278,16 @@ async function withKey(system, fn) {
|
|
|
20281
20278
|
if (!s) throw new Error(`${system} is not connected on this machine.`);
|
|
20282
20279
|
return fn(s.key);
|
|
20283
20280
|
}
|
|
20284
|
-
var
|
|
20281
|
+
var fs11, import_zod50, path10, hintOf, SystemSchema, SummarySourceSchema;
|
|
20285
20282
|
var init_store2 = __esm({
|
|
20286
20283
|
"src/command-os/connectors/store.ts"() {
|
|
20287
20284
|
"use strict";
|
|
20288
|
-
|
|
20285
|
+
fs11 = __toESM(require("node:fs"));
|
|
20289
20286
|
import_zod50 = require("zod");
|
|
20290
|
-
|
|
20287
|
+
path10 = __toESM(require("node:path"));
|
|
20291
20288
|
init_credentials_store();
|
|
20292
20289
|
hintOf = (key) => `\u2026${String(key).slice(-4)}`;
|
|
20293
|
-
SystemSchema = import_zod50.z.enum(["ghl", "stripe", "google-workspace", "meeting-capture", "slack", "
|
|
20290
|
+
SystemSchema = import_zod50.z.enum(["ghl", "stripe", "google-workspace", "meeting-capture", "slack", "byo"]);
|
|
20294
20291
|
SummarySourceSchema = import_zod50.z.object({
|
|
20295
20292
|
system: SystemSchema,
|
|
20296
20293
|
key: import_zod50.z.string().min(1),
|
|
@@ -20524,8 +20521,8 @@ function summarisePrice(sub) {
|
|
|
20524
20521
|
unpriced = true;
|
|
20525
20522
|
continue;
|
|
20526
20523
|
}
|
|
20527
|
-
const
|
|
20528
|
-
const qty = typeof
|
|
20524
|
+
const q3 = i.quantity;
|
|
20525
|
+
const qty = typeof q3 === "number" && isFinite(q3) && q3 > 0 ? Math.round(q3) : 1;
|
|
20529
20526
|
total += unit * qty;
|
|
20530
20527
|
}
|
|
20531
20528
|
if (unpriced) {
|
|
@@ -20764,7 +20761,7 @@ var init_stripe = __esm({
|
|
|
20764
20761
|
// src/command-os/connectors/google-calendar-store.ts
|
|
20765
20762
|
function read() {
|
|
20766
20763
|
try {
|
|
20767
|
-
return SecretSchema.parse(JSON.parse(
|
|
20764
|
+
return SecretSchema.parse(JSON.parse(fs12.readFileSync(googleCalendarPath(), "utf8")));
|
|
20768
20765
|
} catch (error) {
|
|
20769
20766
|
if (error.code === "ENOENT") return null;
|
|
20770
20767
|
throw new Error("The Google Calendar sign-in on this machine could not be read. Connect your Google Calendar again.");
|
|
@@ -20787,16 +20784,16 @@ function saveCalendarSecret(secret) {
|
|
|
20787
20784
|
try {
|
|
20788
20785
|
const validated = SecretSchema.parse(secret);
|
|
20789
20786
|
const dir = ensureAppDataDir();
|
|
20790
|
-
if (process.platform !== "win32")
|
|
20791
|
-
tmp = `${googleCalendarPath()}.${(0,
|
|
20792
|
-
|
|
20793
|
-
|
|
20787
|
+
if (process.platform !== "win32") fs12.chmodSync(dir, 448);
|
|
20788
|
+
tmp = `${googleCalendarPath()}.${(0, import_node_crypto4.randomUUID)()}.tmp`;
|
|
20789
|
+
fs12.writeFileSync(tmp, JSON.stringify(validated) + "\n", { mode: 384, flag: "wx" });
|
|
20790
|
+
fs12.renameSync(tmp, googleCalendarPath());
|
|
20794
20791
|
} catch {
|
|
20795
20792
|
throw new Error("The Google Calendar sign-in could not be saved privately on this machine. Nothing new was saved.");
|
|
20796
20793
|
} finally {
|
|
20797
20794
|
if (tmp) {
|
|
20798
20795
|
try {
|
|
20799
|
-
|
|
20796
|
+
fs12.unlinkSync(tmp);
|
|
20800
20797
|
} catch {
|
|
20801
20798
|
}
|
|
20802
20799
|
}
|
|
@@ -20804,20 +20801,20 @@ function saveCalendarSecret(secret) {
|
|
|
20804
20801
|
}
|
|
20805
20802
|
function removeCalendarSecret() {
|
|
20806
20803
|
try {
|
|
20807
|
-
|
|
20804
|
+
fs12.unlinkSync(googleCalendarPath());
|
|
20808
20805
|
return true;
|
|
20809
20806
|
} catch (error) {
|
|
20810
20807
|
if (error.code === "ENOENT") return false;
|
|
20811
20808
|
throw new Error("The Google Calendar sign-in could not be removed from this machine.");
|
|
20812
20809
|
}
|
|
20813
20810
|
}
|
|
20814
|
-
var
|
|
20811
|
+
var fs12, path11, import_node_crypto4, import_zod51, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
|
|
20815
20812
|
var init_google_calendar_store = __esm({
|
|
20816
20813
|
"src/command-os/connectors/google-calendar-store.ts"() {
|
|
20817
20814
|
"use strict";
|
|
20818
|
-
|
|
20819
|
-
|
|
20820
|
-
|
|
20815
|
+
fs12 = __toESM(require("node:fs"));
|
|
20816
|
+
path11 = __toESM(require("node:path"));
|
|
20817
|
+
import_node_crypto4 = require("node:crypto");
|
|
20821
20818
|
import_zod51 = require("zod");
|
|
20822
20819
|
init_credentials_store();
|
|
20823
20820
|
CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
@@ -20828,7 +20825,7 @@ var init_google_calendar_store = __esm({
|
|
|
20828
20825
|
scope: import_zod51.z.literal(CALENDAR_SCOPE),
|
|
20829
20826
|
connectedAt: import_zod51.z.string()
|
|
20830
20827
|
});
|
|
20831
|
-
googleCalendarPath = () =>
|
|
20828
|
+
googleCalendarPath = () => path11.join(appDataDir(), "google-calendar.json");
|
|
20832
20829
|
}
|
|
20833
20830
|
});
|
|
20834
20831
|
|
|
@@ -20853,7 +20850,7 @@ function calendarSetupDetail() {
|
|
|
20853
20850
|
}
|
|
20854
20851
|
function openBrowser(url) {
|
|
20855
20852
|
const [file2, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
20856
|
-
return new Promise((
|
|
20853
|
+
return new Promise((resolve13) => (0, import_node_child_process.execFile)(file2, args, { timeout: 5e3 }, (error) => resolve13(!error)));
|
|
20857
20854
|
}
|
|
20858
20855
|
function parsed(schema, value) {
|
|
20859
20856
|
const result = schema.safeParse(value);
|
|
@@ -20977,8 +20974,8 @@ function createGoogleCalendar(deps = {}) {
|
|
|
20977
20974
|
async function start() {
|
|
20978
20975
|
const app = appConfig();
|
|
20979
20976
|
clearPending2();
|
|
20980
|
-
const state = (0,
|
|
20981
|
-
const verifier = (0,
|
|
20977
|
+
const state = (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
20978
|
+
const verifier = (0, import_node_crypto5.randomBytes)(32).toString("base64url");
|
|
20982
20979
|
const server2 = (deps.createServer ?? import_node_http.createServer)((req, res) => {
|
|
20983
20980
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
20984
20981
|
res.setHeader("Cache-Control", "no-store");
|
|
@@ -21011,9 +21008,9 @@ function createGoogleCalendar(deps = {}) {
|
|
|
21011
21008
|
server2.requestTimeout = 5e3;
|
|
21012
21009
|
server2.headersTimeout = 5e3;
|
|
21013
21010
|
try {
|
|
21014
|
-
await new Promise((
|
|
21011
|
+
await new Promise((resolve13, reject) => {
|
|
21015
21012
|
server2.once("error", reject);
|
|
21016
|
-
server2.listen(0, "127.0.0.1",
|
|
21013
|
+
server2.listen(0, "127.0.0.1", resolve13);
|
|
21017
21014
|
});
|
|
21018
21015
|
} catch {
|
|
21019
21016
|
server2.close();
|
|
@@ -21032,7 +21029,7 @@ function createGoogleCalendar(deps = {}) {
|
|
|
21032
21029
|
response_type: "code",
|
|
21033
21030
|
scope: CALENDAR_SCOPE,
|
|
21034
21031
|
state,
|
|
21035
|
-
code_challenge: (0,
|
|
21032
|
+
code_challenge: (0, import_node_crypto5.createHash)("sha256").update(verifier).digest("base64url"),
|
|
21036
21033
|
code_challenge_method: "S256",
|
|
21037
21034
|
access_type: "offline",
|
|
21038
21035
|
prompt: "consent"
|
|
@@ -21162,13 +21159,13 @@ function createGoogleCalendar(deps = {}) {
|
|
|
21162
21159
|
close: clearPending2
|
|
21163
21160
|
};
|
|
21164
21161
|
}
|
|
21165
|
-
var import_node_http,
|
|
21162
|
+
var import_node_http, import_node_crypto5, import_node_child_process, import_zod52, TOKEN_URL, REVOKE_URL, API, tokenSchema, calendarSchema, eventSchema;
|
|
21166
21163
|
var init_google_calendar = __esm({
|
|
21167
21164
|
"src/command-os/connectors/google-calendar.ts"() {
|
|
21168
21165
|
"use strict";
|
|
21169
21166
|
init_any_signal();
|
|
21170
21167
|
import_node_http = require("node:http");
|
|
21171
|
-
|
|
21168
|
+
import_node_crypto5 = require("node:crypto");
|
|
21172
21169
|
import_node_child_process = require("node:child_process");
|
|
21173
21170
|
import_zod52 = require("zod");
|
|
21174
21171
|
init_credentials_store();
|
|
@@ -21383,7 +21380,7 @@ var init_stripe_read = __esm({
|
|
|
21383
21380
|
// src/client-engagements.ts
|
|
21384
21381
|
function clientEngagementsPath() {
|
|
21385
21382
|
const override = process.env[CLIENT_ENGAGEMENTS_ENV];
|
|
21386
|
-
return override && override.trim() ? override :
|
|
21383
|
+
return override && override.trim() ? override : path12.join(appDataDir(), "client-engagements.json");
|
|
21387
21384
|
}
|
|
21388
21385
|
function isAgencyClient(e) {
|
|
21389
21386
|
return (e.kind ?? DEFAULT_ENGAGEMENT_KIND) === "client";
|
|
@@ -21434,15 +21431,15 @@ function sanitizeEngagements(raw) {
|
|
|
21434
21431
|
for (const [id, rec] of Object.entries(src).slice(0, MAX_CLIENTS)) {
|
|
21435
21432
|
const key = str2(id, 64);
|
|
21436
21433
|
if (!key || FORBIDDEN_KEY.has(key)) continue;
|
|
21437
|
-
const
|
|
21438
|
-
if (
|
|
21434
|
+
const clean2 = sanitizeEngagement(rec);
|
|
21435
|
+
if (clean2) clients[key] = clean2;
|
|
21439
21436
|
}
|
|
21440
21437
|
}
|
|
21441
21438
|
return { v: 1, clients: { ...clients }, updated_at: str2(r.updated_at, 40) };
|
|
21442
21439
|
}
|
|
21443
21440
|
function readClientEngagements(file2 = clientEngagementsPath()) {
|
|
21444
21441
|
try {
|
|
21445
|
-
const raw = JSON.parse(
|
|
21442
|
+
const raw = JSON.parse(fs13.readFileSync(file2, "utf8"));
|
|
21446
21443
|
if (raw && typeof raw === "object") return sanitizeEngagements(raw);
|
|
21447
21444
|
} catch {
|
|
21448
21445
|
}
|
|
@@ -21469,7 +21466,7 @@ function writeClientEngagement(locationId2, patch, file2 = clientEngagementsPath
|
|
|
21469
21466
|
if (!id || FORBIDDEN_KEY.has(id)) throw new Error("An id is required.");
|
|
21470
21467
|
const stamp = () => {
|
|
21471
21468
|
try {
|
|
21472
|
-
return
|
|
21469
|
+
return fs13.readFileSync(file2, "utf8");
|
|
21473
21470
|
} catch {
|
|
21474
21471
|
return "";
|
|
21475
21472
|
}
|
|
@@ -21489,20 +21486,20 @@ function writeClientEngagement(locationId2, patch, file2 = clientEngagementsPath
|
|
|
21489
21486
|
};
|
|
21490
21487
|
const persist = (next) => {
|
|
21491
21488
|
if (file2 === clientEngagementsPath() && !process.env[CLIENT_ENGAGEMENTS_ENV]) ensureAppDataDir();
|
|
21492
|
-
|
|
21489
|
+
fs13.mkdirSync(path12.dirname(file2), { recursive: true });
|
|
21493
21490
|
const tmp = `${file2}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
21494
21491
|
try {
|
|
21495
|
-
|
|
21492
|
+
fs13.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
21496
21493
|
if (process.platform !== "win32") {
|
|
21497
21494
|
try {
|
|
21498
|
-
|
|
21495
|
+
fs13.chmodSync(tmp, 384);
|
|
21499
21496
|
} catch {
|
|
21500
21497
|
}
|
|
21501
21498
|
}
|
|
21502
|
-
|
|
21499
|
+
fs13.renameSync(tmp, file2);
|
|
21503
21500
|
} catch (e) {
|
|
21504
21501
|
try {
|
|
21505
|
-
|
|
21502
|
+
fs13.unlinkSync(tmp);
|
|
21506
21503
|
} catch {
|
|
21507
21504
|
}
|
|
21508
21505
|
throw e;
|
|
@@ -21574,12 +21571,12 @@ function monthlyFloor(roster, profile) {
|
|
|
21574
21571
|
byKind
|
|
21575
21572
|
};
|
|
21576
21573
|
}
|
|
21577
|
-
var
|
|
21574
|
+
var fs13, path12, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, ENGAGEMENT_KINDS, DEFAULT_ENGAGEMENT_KIND, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
|
|
21578
21575
|
var init_client_engagements = __esm({
|
|
21579
21576
|
"src/client-engagements.ts"() {
|
|
21580
21577
|
"use strict";
|
|
21581
|
-
|
|
21582
|
-
|
|
21578
|
+
fs13 = __toESM(require("fs"));
|
|
21579
|
+
path12 = __toESM(require("path"));
|
|
21583
21580
|
init_credentials_store();
|
|
21584
21581
|
init_field_sanitize();
|
|
21585
21582
|
CLIENT_ENGAGEMENTS_ENV = "GHL_MCP_CLIENT_ENGAGEMENTS";
|
|
@@ -21597,7 +21594,7 @@ var init_client_engagements = __esm({
|
|
|
21597
21594
|
// src/board-roster.ts
|
|
21598
21595
|
function readBoardState() {
|
|
21599
21596
|
try {
|
|
21600
|
-
const raw = JSON.parse(
|
|
21597
|
+
const raw = JSON.parse(fs14.readFileSync(path13.join(appDataDir(), "cockpit-state.json"), "utf8"));
|
|
21601
21598
|
if (raw && typeof raw === "object" && raw.clients && typeof raw.clients === "object") return raw;
|
|
21602
21599
|
} catch {
|
|
21603
21600
|
}
|
|
@@ -21615,12 +21612,12 @@ function knownClients(registry2, state) {
|
|
|
21615
21612
|
}
|
|
21616
21613
|
return [...out.values()];
|
|
21617
21614
|
}
|
|
21618
|
-
var
|
|
21615
|
+
var fs14, path13;
|
|
21619
21616
|
var init_board_roster = __esm({
|
|
21620
21617
|
"src/board-roster.ts"() {
|
|
21621
21618
|
"use strict";
|
|
21622
|
-
|
|
21623
|
-
|
|
21619
|
+
fs14 = __toESM(require("fs"));
|
|
21620
|
+
path13 = __toESM(require("path"));
|
|
21624
21621
|
init_credentials_store();
|
|
21625
21622
|
init_client_engagements();
|
|
21626
21623
|
}
|
|
@@ -21835,17 +21832,17 @@ var init_stage_boundary = __esm({
|
|
|
21835
21832
|
// src/plan-guide.ts
|
|
21836
21833
|
function skillRoot() {
|
|
21837
21834
|
const candidates = [
|
|
21838
|
-
|
|
21835
|
+
path14.join(__dirname, "..", "skills", "blueprint"),
|
|
21839
21836
|
// dist/ → package root
|
|
21840
|
-
|
|
21837
|
+
path14.join(__dirname, "..", "..", "skills", "blueprint"),
|
|
21841
21838
|
// src/ in dev
|
|
21842
|
-
|
|
21839
|
+
path14.join(process.cwd(), "skills", "blueprint")
|
|
21843
21840
|
];
|
|
21844
|
-
return candidates.find((c) =>
|
|
21841
|
+
return candidates.find((c) => fs15.existsSync(path14.join(c, "references", "build-plan-schema.md"))) ?? null;
|
|
21845
21842
|
}
|
|
21846
21843
|
function readIf(p, max) {
|
|
21847
21844
|
try {
|
|
21848
|
-
const t =
|
|
21845
|
+
const t = fs15.readFileSync(p, "utf8");
|
|
21849
21846
|
return t.length > max ? t.slice(0, max) : t;
|
|
21850
21847
|
} catch {
|
|
21851
21848
|
return null;
|
|
@@ -21858,12 +21855,12 @@ function presetForIndustry(industry) {
|
|
|
21858
21855
|
function loadPlanGuide(industry) {
|
|
21859
21856
|
const root = skillRoot();
|
|
21860
21857
|
if (!root) return "";
|
|
21861
|
-
const schema = readIf(
|
|
21858
|
+
const schema = readIf(path14.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
|
|
21862
21859
|
const presetName = presetForIndustry(industry);
|
|
21863
|
-
const preset = readIf(
|
|
21860
|
+
const preset = readIf(path14.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
|
|
21864
21861
|
const exampleFile = EXAMPLE_FOR_PRESET[presetName] ?? "sample-build-plan.json";
|
|
21865
|
-
const example = readIf(
|
|
21866
|
-
const copyGuide = readIf(
|
|
21862
|
+
const example = readIf(path14.join(root, "examples", exampleFile), CAP_EXAMPLE);
|
|
21863
|
+
const copyGuide = readIf(path14.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
|
|
21867
21864
|
if (!schema) return "";
|
|
21868
21865
|
return `
|
|
21869
21866
|
PLAN GUIDE \u2014 everything you need to write the build plan is below. Do NOT try to read files; you cannot.
|
|
@@ -21881,12 +21878,12 @@ ${example}
|
|
|
21881
21878
|
` : "") + `=== End of plan guide ===
|
|
21882
21879
|
`;
|
|
21883
21880
|
}
|
|
21884
|
-
var
|
|
21881
|
+
var fs15, path14, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
|
|
21885
21882
|
var init_plan_guide = __esm({
|
|
21886
21883
|
"src/plan-guide.ts"() {
|
|
21887
21884
|
"use strict";
|
|
21888
|
-
|
|
21889
|
-
|
|
21885
|
+
fs15 = __toESM(require("fs"));
|
|
21886
|
+
path14 = __toESM(require("path"));
|
|
21890
21887
|
PRESET_FOR_INDUSTRY = {
|
|
21891
21888
|
"med-spa": "med-spa",
|
|
21892
21889
|
clinic: "clinic",
|
|
@@ -21916,16 +21913,16 @@ function deriveFieldKey(fieldName) {
|
|
|
21916
21913
|
function intakeFieldName(label) {
|
|
21917
21914
|
return `${INTAKE_FIELD_NAME_PREFIX}${label}`;
|
|
21918
21915
|
}
|
|
21919
|
-
function expectedFieldKey(
|
|
21920
|
-
return deriveFieldKey(intakeFieldName(
|
|
21916
|
+
function expectedFieldKey(q3) {
|
|
21917
|
+
return deriveFieldKey(intakeFieldName(q3.label));
|
|
21921
21918
|
}
|
|
21922
21919
|
function intakeSectionCount(questions = INTAKE_QUESTIONS) {
|
|
21923
21920
|
let count2 = 0;
|
|
21924
21921
|
let last;
|
|
21925
|
-
for (const
|
|
21926
|
-
if (
|
|
21922
|
+
for (const q3 of questions) {
|
|
21923
|
+
if (q3.section !== last) {
|
|
21927
21924
|
count2++;
|
|
21928
|
-
last =
|
|
21925
|
+
last = q3.section;
|
|
21929
21926
|
}
|
|
21930
21927
|
}
|
|
21931
21928
|
return count2;
|
|
@@ -24506,10 +24503,10 @@ function progressLine(e) {
|
|
|
24506
24503
|
}
|
|
24507
24504
|
}
|
|
24508
24505
|
function defaultProgressDir(base = appDataDir()) {
|
|
24509
|
-
return
|
|
24506
|
+
return path15.join(base, "progress");
|
|
24510
24507
|
}
|
|
24511
24508
|
function progressFilePath(dir, locationId2) {
|
|
24512
|
-
return
|
|
24509
|
+
return path15.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
|
|
24513
24510
|
}
|
|
24514
24511
|
function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DIR_ENV]) {
|
|
24515
24512
|
if (!dir) return null;
|
|
@@ -24517,10 +24514,10 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
|
|
|
24517
24514
|
const file2 = progressFilePath(dir, locationId2);
|
|
24518
24515
|
const write = () => {
|
|
24519
24516
|
try {
|
|
24520
|
-
|
|
24517
|
+
fs16.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
24521
24518
|
const tmp = `${file2}.tmp`;
|
|
24522
|
-
|
|
24523
|
-
|
|
24519
|
+
fs16.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
|
|
24520
|
+
fs16.renameSync(tmp, file2);
|
|
24524
24521
|
} catch {
|
|
24525
24522
|
}
|
|
24526
24523
|
};
|
|
@@ -24538,7 +24535,7 @@ function createProgressFileSink(locationId2, plan, dir = process.env[PROGRESS_DI
|
|
|
24538
24535
|
}
|
|
24539
24536
|
function readProgressSnapshot(dir, locationId2) {
|
|
24540
24537
|
try {
|
|
24541
|
-
const raw = JSON.parse(
|
|
24538
|
+
const raw = JSON.parse(fs16.readFileSync(progressFilePath(dir, locationId2), "utf8"));
|
|
24542
24539
|
const p = raw?.progress;
|
|
24543
24540
|
if (!p || !Array.isArray(p.groups) || typeof p.updatedAt !== "string") return null;
|
|
24544
24541
|
return p;
|
|
@@ -24567,12 +24564,12 @@ function tapPush(arr, onItem) {
|
|
|
24567
24564
|
};
|
|
24568
24565
|
Object.defineProperty(arr, "push", { value: tapped, writable: true, configurable: true, enumerable: false });
|
|
24569
24566
|
}
|
|
24570
|
-
var
|
|
24567
|
+
var fs16, path15, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
|
|
24571
24568
|
var init_build_progress = __esm({
|
|
24572
24569
|
"src/build-progress.ts"() {
|
|
24573
24570
|
"use strict";
|
|
24574
|
-
|
|
24575
|
-
|
|
24571
|
+
fs16 = __toESM(require("fs"));
|
|
24572
|
+
path15 = __toESM(require("path"));
|
|
24576
24573
|
init_executor();
|
|
24577
24574
|
init_credentials_store();
|
|
24578
24575
|
PROGRESS_GROUPS = [
|
|
@@ -24710,10 +24707,10 @@ var init_build_progress = __esm({
|
|
|
24710
24707
|
|
|
24711
24708
|
// src/build-journal.ts
|
|
24712
24709
|
function journalDir(base = appDataDir()) {
|
|
24713
|
-
return
|
|
24710
|
+
return path16.join(base, "build-journal");
|
|
24714
24711
|
}
|
|
24715
24712
|
function journalPath(locationId2, base) {
|
|
24716
|
-
return
|
|
24713
|
+
return path16.join(journalDir(base), `${locationId2}.jsonl`);
|
|
24717
24714
|
}
|
|
24718
24715
|
function newRunId(now = /* @__PURE__ */ new Date(), rand = defaultRand) {
|
|
24719
24716
|
const iso2 = now.toISOString();
|
|
@@ -24787,40 +24784,40 @@ function directoryFlushUnsupported(e) {
|
|
|
24787
24784
|
}
|
|
24788
24785
|
function appendJournal(record, base) {
|
|
24789
24786
|
const dir = journalDir(base);
|
|
24790
|
-
|
|
24787
|
+
fs17.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
24791
24788
|
const file2 = journalPath(record.locationId, base);
|
|
24792
24789
|
let needsBoundary = false;
|
|
24793
24790
|
try {
|
|
24794
|
-
const st =
|
|
24791
|
+
const st = fs17.statSync(file2);
|
|
24795
24792
|
if (st.size > 0) {
|
|
24796
|
-
const fd2 =
|
|
24793
|
+
const fd2 = fs17.openSync(file2, "r");
|
|
24797
24794
|
try {
|
|
24798
24795
|
const buf = Buffer.alloc(1);
|
|
24799
|
-
|
|
24796
|
+
fs17.readSync(fd2, buf, 0, 1, st.size - 1);
|
|
24800
24797
|
needsBoundary = buf[0] !== 10;
|
|
24801
24798
|
} finally {
|
|
24802
|
-
|
|
24799
|
+
fs17.closeSync(fd2);
|
|
24803
24800
|
}
|
|
24804
24801
|
}
|
|
24805
24802
|
} catch {
|
|
24806
24803
|
}
|
|
24807
|
-
const fd =
|
|
24804
|
+
const fd = fs17.openSync(file2, "a", 384);
|
|
24808
24805
|
try {
|
|
24809
|
-
|
|
24806
|
+
fs17.appendFileSync(fd, `${needsBoundary ? "\n" : ""}${JSON.stringify(record)}
|
|
24810
24807
|
`);
|
|
24811
|
-
|
|
24808
|
+
fs17.fsyncSync(fd);
|
|
24812
24809
|
} finally {
|
|
24813
|
-
|
|
24810
|
+
fs17.closeSync(fd);
|
|
24814
24811
|
}
|
|
24815
|
-
for (const directory of [dir,
|
|
24812
|
+
for (const directory of [dir, path16.dirname(dir)]) {
|
|
24816
24813
|
let dirFd;
|
|
24817
24814
|
try {
|
|
24818
|
-
dirFd =
|
|
24819
|
-
|
|
24815
|
+
dirFd = fs17.openSync(directory, "r");
|
|
24816
|
+
fs17.fsyncSync(dirFd);
|
|
24820
24817
|
} catch (e) {
|
|
24821
24818
|
if (!directoryFlushUnsupported(e)) throw e;
|
|
24822
24819
|
} finally {
|
|
24823
|
-
if (dirFd !== void 0)
|
|
24820
|
+
if (dirFd !== void 0) fs17.closeSync(dirFd);
|
|
24824
24821
|
}
|
|
24825
24822
|
}
|
|
24826
24823
|
return file2;
|
|
@@ -24828,7 +24825,7 @@ function appendJournal(record, base) {
|
|
|
24828
24825
|
function readJournal(locationId2, base) {
|
|
24829
24826
|
let raw;
|
|
24830
24827
|
try {
|
|
24831
|
-
raw =
|
|
24828
|
+
raw = fs17.readFileSync(journalPath(locationId2, base), "utf8");
|
|
24832
24829
|
} catch {
|
|
24833
24830
|
return [];
|
|
24834
24831
|
}
|
|
@@ -24899,12 +24896,12 @@ function describeRuns(locationId2, base) {
|
|
|
24899
24896
|
reverted: reverted.has(r.runId)
|
|
24900
24897
|
})).reverse();
|
|
24901
24898
|
}
|
|
24902
|
-
var
|
|
24899
|
+
var fs17, path16, DIRECTORY_FLUSH_UNSUPPORTED;
|
|
24903
24900
|
var init_build_journal = __esm({
|
|
24904
24901
|
"src/build-journal.ts"() {
|
|
24905
24902
|
"use strict";
|
|
24906
|
-
|
|
24907
|
-
|
|
24903
|
+
fs17 = __toESM(require("fs"));
|
|
24904
|
+
path16 = __toESM(require("path"));
|
|
24908
24905
|
init_credentials_store();
|
|
24909
24906
|
DIRECTORY_FLUSH_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EINVAL", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EISDIR", "EBADF"]);
|
|
24910
24907
|
}
|
|
@@ -24916,7 +24913,7 @@ function unusableShimMessage(shim) {
|
|
|
24916
24913
|
}
|
|
24917
24914
|
function runnable(p) {
|
|
24918
24915
|
try {
|
|
24919
|
-
|
|
24916
|
+
fs18.accessSync(p, fs18.constants.X_OK);
|
|
24920
24917
|
return true;
|
|
24921
24918
|
} catch {
|
|
24922
24919
|
return false;
|
|
@@ -24928,27 +24925,27 @@ function claudeFileNames(platform2, env2) {
|
|
|
24928
24925
|
return [...exts.map((ext) => `claude${ext}`), "claude"];
|
|
24929
24926
|
}
|
|
24930
24927
|
function isShim(file2) {
|
|
24931
|
-
const ext =
|
|
24928
|
+
const ext = path17.extname(file2).toLowerCase();
|
|
24932
24929
|
return ext === ".cmd" || ext === ".bat";
|
|
24933
24930
|
}
|
|
24934
24931
|
function shimTarget(shim) {
|
|
24935
24932
|
let text2;
|
|
24936
24933
|
try {
|
|
24937
|
-
text2 =
|
|
24934
|
+
text2 = fs18.readFileSync(shim, "utf8");
|
|
24938
24935
|
} catch {
|
|
24939
24936
|
return null;
|
|
24940
24937
|
}
|
|
24941
24938
|
const refs = [...text2.matchAll(/"%~?dp0%?\\([^"\r\n]+)"/g)].map((m) => m[1]);
|
|
24942
|
-
const
|
|
24939
|
+
const resolve13 = (rel) => path17.join(path17.dirname(shim), ...rel.split(/[\\/]/));
|
|
24943
24940
|
const script = refs.find((r) => /\.(?:js|cjs|mjs)$/i.test(r));
|
|
24944
24941
|
if (script) {
|
|
24945
|
-
const p =
|
|
24946
|
-
return
|
|
24942
|
+
const p = resolve13(script);
|
|
24943
|
+
return fs18.existsSync(p) ? { kind: "script", path: p } : null;
|
|
24947
24944
|
}
|
|
24948
24945
|
const program = refs.find((r) => /\.exe$/i.test(r) && !/(^|[\\/])node\.exe$/i.test(r));
|
|
24949
24946
|
if (program) {
|
|
24950
|
-
const p =
|
|
24951
|
-
return
|
|
24947
|
+
const p = resolve13(program);
|
|
24948
|
+
return fs18.existsSync(p) ? { kind: "program", path: p } : null;
|
|
24952
24949
|
}
|
|
24953
24950
|
return null;
|
|
24954
24951
|
}
|
|
@@ -24961,7 +24958,7 @@ function claudeBin(lookup = {}) {
|
|
|
24961
24958
|
let unusableShim = null;
|
|
24962
24959
|
for (const dir of dirs) {
|
|
24963
24960
|
for (const name of names) {
|
|
24964
|
-
const candidate =
|
|
24961
|
+
const candidate = path17.join(dir, name);
|
|
24965
24962
|
if (!runnable(candidate)) continue;
|
|
24966
24963
|
if (platform2 === "win32" && isShim(candidate) && !shimTarget(candidate)) {
|
|
24967
24964
|
unusableShim ??= candidate;
|
|
@@ -24970,7 +24967,7 @@ function claudeBin(lookup = {}) {
|
|
|
24970
24967
|
return candidate;
|
|
24971
24968
|
}
|
|
24972
24969
|
}
|
|
24973
|
-
const fallback =
|
|
24970
|
+
const fallback = path17.join(home, ".local", "bin", platform2 === "win32" ? "claude.exe" : "claude");
|
|
24974
24971
|
if (runnable(fallback)) return fallback;
|
|
24975
24972
|
throw new Error(unusableShim ? unusableShimMessage(unusableShim) : NOT_FOUND_MESSAGE);
|
|
24976
24973
|
}
|
|
@@ -24990,13 +24987,13 @@ function claudeCommand(bin, args, lookup = {}) {
|
|
|
24990
24987
|
if (!target) return { command: resolved2, args };
|
|
24991
24988
|
return target.kind === "script" ? { command: lookup.execPath ?? process.execPath, args: [target.path, ...args] } : { command: target.path, args };
|
|
24992
24989
|
}
|
|
24993
|
-
var
|
|
24990
|
+
var fs18, os3, path17, NOT_FOUND_MESSAGE;
|
|
24994
24991
|
var init_claude_locate = __esm({
|
|
24995
24992
|
"src/claude-locate.ts"() {
|
|
24996
24993
|
"use strict";
|
|
24997
|
-
|
|
24994
|
+
fs18 = __toESM(require("node:fs"));
|
|
24998
24995
|
os3 = __toESM(require("node:os"));
|
|
24999
|
-
|
|
24996
|
+
path17 = __toESM(require("node:path"));
|
|
25000
24997
|
NOT_FOUND_MESSAGE = "Claude Code CLI not found. Install it, then reopen the cockpit.";
|
|
25001
24998
|
}
|
|
25002
24999
|
});
|
|
@@ -25032,21 +25029,21 @@ function tooOldMessage(found) {
|
|
|
25032
25029
|
return `Command OS needs Claude Code ${MIN_CLAUDE_VERSION} or newer to run a stage. ${seen} Update Claude Code, then reopen Command OS. Nothing was started and nothing was spent.`;
|
|
25033
25030
|
}
|
|
25034
25031
|
function enginePath() {
|
|
25035
|
-
return
|
|
25032
|
+
return path18.join(appDataDir(), "command-os-engine.json");
|
|
25036
25033
|
}
|
|
25037
25034
|
function readEngineRecord(file2 = enginePath()) {
|
|
25038
25035
|
try {
|
|
25039
|
-
const r = JSON.parse(
|
|
25036
|
+
const r = JSON.parse(fs19.readFileSync(file2, "utf8"));
|
|
25040
25037
|
return typeof r?.version === "string" ? r : null;
|
|
25041
25038
|
} catch {
|
|
25042
25039
|
return null;
|
|
25043
25040
|
}
|
|
25044
25041
|
}
|
|
25045
25042
|
function writeEngineRecord(rec, file2 = enginePath()) {
|
|
25046
|
-
|
|
25047
|
-
|
|
25043
|
+
fs19.mkdirSync(path18.dirname(file2), { recursive: true });
|
|
25044
|
+
fs19.writeFileSync(file2, JSON.stringify(rec, null, 2), { mode: 384 });
|
|
25048
25045
|
try {
|
|
25049
|
-
|
|
25046
|
+
fs19.chmodSync(file2, 384);
|
|
25050
25047
|
} catch {
|
|
25051
25048
|
}
|
|
25052
25049
|
}
|
|
@@ -25069,12 +25066,12 @@ function noteObservedEngine(observed, file2 = enginePath()) {
|
|
|
25069
25066
|
return true;
|
|
25070
25067
|
}
|
|
25071
25068
|
function runSmoke(bin, timeoutMs = 9e4) {
|
|
25072
|
-
return new Promise((
|
|
25069
|
+
return new Promise((resolve13) => {
|
|
25073
25070
|
let settled = false;
|
|
25074
25071
|
const done = (r) => {
|
|
25075
25072
|
if (!settled) {
|
|
25076
25073
|
settled = true;
|
|
25077
|
-
|
|
25074
|
+
resolve13(r);
|
|
25078
25075
|
}
|
|
25079
25076
|
};
|
|
25080
25077
|
const cmd = claudeCommand(bin, [
|
|
@@ -25185,13 +25182,13 @@ async function ensureEngineReady(bin, onNote = () => {
|
|
|
25185
25182
|
onNote(`Claude Code ${version} checked out${smoke.model ? ` (${smoke.model})` : ""}. Starting the step.`);
|
|
25186
25183
|
return { ok: true, version, smoked: true, smokeCostUsd: smoke.costUsd, smokeModel: smoke.model };
|
|
25187
25184
|
}
|
|
25188
|
-
var import_child_process2,
|
|
25185
|
+
var import_child_process2, fs19, path18, MIN_CLAUDE_VERSION, SMOKE_PROMPT;
|
|
25189
25186
|
var init_claude_cli = __esm({
|
|
25190
25187
|
"src/claude-cli.ts"() {
|
|
25191
25188
|
"use strict";
|
|
25192
25189
|
import_child_process2 = require("child_process");
|
|
25193
|
-
|
|
25194
|
-
|
|
25190
|
+
fs19 = __toESM(require("fs"));
|
|
25191
|
+
path18 = __toESM(require("path"));
|
|
25195
25192
|
init_credentials_store();
|
|
25196
25193
|
init_claude_locate();
|
|
25197
25194
|
MIN_CLAUDE_VERSION = "2.1.0";
|
|
@@ -25299,20 +25296,20 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
25299
25296
|
const locationName = SANDBOX_ALLOWLIST[locationId2] || opts.name || locationId2;
|
|
25300
25297
|
const spec = opts.spec ?? STAGE_SPECS[stage];
|
|
25301
25298
|
if (!spec) return Promise.reject(new Error(`Stage ${stage} is not powered yet.`));
|
|
25302
|
-
return new Promise((
|
|
25299
|
+
return new Promise((resolve13) => {
|
|
25303
25300
|
void (async () => {
|
|
25304
25301
|
const bin = claudeBin();
|
|
25305
25302
|
const engineCheck = await ensureEngineReady(bin, (line3) => onEvent({ kind: "status", line: line3 }));
|
|
25306
25303
|
if (!engineCheck.ok) {
|
|
25307
25304
|
onEvent({ kind: "error", line: engineCheck.reason });
|
|
25308
|
-
|
|
25305
|
+
resolve13({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
|
|
25309
25306
|
return;
|
|
25310
25307
|
}
|
|
25311
25308
|
onEvent({ kind: "status", line: `Starting ${spec.name} for ${locationName}.` });
|
|
25312
25309
|
const progressDir = opts.onProgress ? defaultProgressDir() : void 0;
|
|
25313
25310
|
if (progressDir) {
|
|
25314
25311
|
try {
|
|
25315
|
-
|
|
25312
|
+
fs20.rmSync(progressFilePath(progressDir, locationId2), { force: true });
|
|
25316
25313
|
} catch {
|
|
25317
25314
|
}
|
|
25318
25315
|
}
|
|
@@ -25397,7 +25394,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
25397
25394
|
if (code !== 0 && !lastText) {
|
|
25398
25395
|
const outcome2 = { ok: false, error: `claude exited ${code}: ${stderrTail.trim().slice(-200) || "no output"}`, ...engine };
|
|
25399
25396
|
onEvent({ kind: "error", line: outcome2.error });
|
|
25400
|
-
|
|
25397
|
+
resolve13(outcome2);
|
|
25401
25398
|
return;
|
|
25402
25399
|
}
|
|
25403
25400
|
const outcome = { ...parseResultLine(lastText), ...engine };
|
|
@@ -25410,29 +25407,29 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
|
|
|
25410
25407
|
const detail = outcome.error || (outcome.issues?.length ? outcome.issues.join(" \xB7 ") : "") || outcome.summary || "no detail reported";
|
|
25411
25408
|
const success = outcome.formId ? `Done: form "${outcome.formName}" (${outcome.formId})` : `Done: ${outcome.summary ?? "stage complete"}`;
|
|
25412
25409
|
onEvent({ kind: "result", line: outcome.ok ? success : `Not passed: ${detail}` });
|
|
25413
|
-
|
|
25410
|
+
resolve13(outcome);
|
|
25414
25411
|
});
|
|
25415
25412
|
child.on("error", (err) => {
|
|
25416
25413
|
clearTimeout(timeout);
|
|
25417
25414
|
if (progressPoll) clearInterval(progressPoll);
|
|
25418
25415
|
const outcome = { ok: false, error: `could not start claude: ${err.message}` };
|
|
25419
25416
|
onEvent({ kind: "error", line: outcome.error });
|
|
25420
|
-
|
|
25417
|
+
resolve13(outcome);
|
|
25421
25418
|
});
|
|
25422
25419
|
})().catch((err) => {
|
|
25423
25420
|
const line3 = err instanceof Error ? err.message : String(err);
|
|
25424
25421
|
onEvent({ kind: "error", line: line3 });
|
|
25425
|
-
|
|
25422
|
+
resolve13({ ok: false, error: line3 });
|
|
25426
25423
|
});
|
|
25427
25424
|
});
|
|
25428
25425
|
}
|
|
25429
|
-
var import_child_process3,
|
|
25426
|
+
var import_child_process3, fs20, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
|
|
25430
25427
|
var init_stage_runner = __esm({
|
|
25431
25428
|
"src/stage-runner.ts"() {
|
|
25432
25429
|
"use strict";
|
|
25433
25430
|
init_intake_notification();
|
|
25434
25431
|
import_child_process3 = require("child_process");
|
|
25435
|
-
|
|
25432
|
+
fs20 = __toESM(require("fs"));
|
|
25436
25433
|
init_stage_boundary();
|
|
25437
25434
|
init_plan_guide();
|
|
25438
25435
|
init_question_set();
|
|
@@ -26282,8 +26279,8 @@ function lintDesignContract(html2, medium) {
|
|
|
26282
26279
|
for (let i = 0; i < sectionTags.length; i++) {
|
|
26283
26280
|
const start = sectionTags[i].index;
|
|
26284
26281
|
const end = i + 1 < sectionTags.length ? sectionTags[i + 1].index : html2.length;
|
|
26285
|
-
const
|
|
26286
|
-
headlinesPerSection.push(
|
|
26282
|
+
const inside3 = opens.filter((t) => t.index > start && t.index < end);
|
|
26283
|
+
headlinesPerSection.push(inside3.filter((t) => t.attrs["data-role"] === "headline").length);
|
|
26287
26284
|
}
|
|
26288
26285
|
for (const t of opens) {
|
|
26289
26286
|
const r = t.attrs["data-role"];
|
|
@@ -26621,13 +26618,13 @@ var init_handoff_pack = __esm({
|
|
|
26621
26618
|
|
|
26622
26619
|
// src/command-os/handoff-run.ts
|
|
26623
26620
|
function askNoTools(prompt, opts = {}) {
|
|
26624
|
-
return new Promise((
|
|
26621
|
+
return new Promise((resolve13) => {
|
|
26625
26622
|
let bin;
|
|
26626
26623
|
try {
|
|
26627
26624
|
bin = claudeBin();
|
|
26628
26625
|
} catch {
|
|
26629
26626
|
opts.onCost?.();
|
|
26630
|
-
|
|
26627
|
+
resolve13("");
|
|
26631
26628
|
return;
|
|
26632
26629
|
}
|
|
26633
26630
|
const cmd = claudeCommand(bin, ["-p", prompt, "--output-format", "stream-json", "--verbose", "--max-turns", String(opts.maxTurns ?? 3), "--allowedTools", "", "--disallowedTools", NO_TOOLS_DENY]);
|
|
@@ -26665,12 +26662,12 @@ function askNoTools(prompt, opts = {}) {
|
|
|
26665
26662
|
child.on("error", () => {
|
|
26666
26663
|
clearTimeout(timeout);
|
|
26667
26664
|
report2();
|
|
26668
|
-
|
|
26665
|
+
resolve13(lastText);
|
|
26669
26666
|
});
|
|
26670
26667
|
child.on("close", () => {
|
|
26671
26668
|
clearTimeout(timeout);
|
|
26672
26669
|
report2();
|
|
26673
|
-
|
|
26670
|
+
resolve13(lastText);
|
|
26674
26671
|
});
|
|
26675
26672
|
});
|
|
26676
26673
|
}
|
|
@@ -27763,8 +27760,8 @@ function dailyBrief(input) {
|
|
|
27763
27760
|
} else if (renewals.length) {
|
|
27764
27761
|
headline3 = `${renewals[0].client}'s agreement renews ${renewals[0].inWords}.`;
|
|
27765
27762
|
} else if (payingButQuiet.length) {
|
|
27766
|
-
const
|
|
27767
|
-
headline3 = payingButQuiet.length === 1 ? `${
|
|
27763
|
+
const q3 = payingButQuiet[0];
|
|
27764
|
+
headline3 = payingButQuiet.length === 1 ? `${q3.client} is paying you and has booked nothing in ${q3.days} days.` : `${payingButQuiet.length} clients are paying you and have booked nothing in ${q3.days} days. The largest is ${q3.client}.`;
|
|
27768
27765
|
} else if (underFloor.length) {
|
|
27769
27766
|
headline3 = `Nothing is waiting, but ${underFloor.length} client${underFloor.length === 1 ? " is" : "s are"} billed below your own floor.`;
|
|
27770
27767
|
} else if (newLeads.length) {
|
|
@@ -28094,7 +28091,7 @@ function stopReasonSentence(reason3, fetched, cap) {
|
|
|
28094
28091
|
}
|
|
28095
28092
|
}
|
|
28096
28093
|
function delay(ms) {
|
|
28097
|
-
return new Promise((
|
|
28094
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
28098
28095
|
}
|
|
28099
28096
|
async function fetchAllContacts(client, locationId2, options = {}) {
|
|
28100
28097
|
const cap = options.maxContacts ?? DEFAULT_MAX_CONTACTS;
|
|
@@ -28509,19 +28506,6 @@ async function readDailyBrief(registry2, options = {}) {
|
|
|
28509
28506
|
return { brief, ownDay, accounts: { total: roster.filter((r) => !r.pending).length, unread: liveRead?.unreachable?.length ?? 0 } };
|
|
28510
28507
|
}
|
|
28511
28508
|
function registerDailyBriefTools(server2, registry2) {
|
|
28512
|
-
const recordTool = server2.tool(
|
|
28513
|
-
"record_brief_answers",
|
|
28514
|
-
"Save up to 50 Gmail or Google Drive findings from a chat brief as a private file on this machine, one source per call; replaces that source's previous rows and makes no provider calls. The cockpit's Daily brief page does not display them. Call it only when the user asks to save them.",
|
|
28515
|
-
BriefAnswersInput.shape,
|
|
28516
|
-
async (input) => {
|
|
28517
|
-
try {
|
|
28518
|
-
return jsonResponse(recordBriefAnswers(input));
|
|
28519
|
-
} catch (error) {
|
|
28520
|
-
return errorResponse(error);
|
|
28521
|
-
}
|
|
28522
|
-
}
|
|
28523
|
-
);
|
|
28524
|
-
if (recordTool) recordTool.inputSchema = BriefAnswersInput;
|
|
28525
28509
|
safeTool(
|
|
28526
28510
|
server2,
|
|
28527
28511
|
"daily_brief",
|
|
@@ -28542,7 +28526,6 @@ var init_daily_brief2 = __esm({
|
|
|
28542
28526
|
"src/tools/daily-brief.ts"() {
|
|
28543
28527
|
"use strict";
|
|
28544
28528
|
import_zod56 = require("zod");
|
|
28545
|
-
init_brief_answers();
|
|
28546
28529
|
init_tool_helpers();
|
|
28547
28530
|
init_ghl_client();
|
|
28548
28531
|
init_agency_profile();
|
|
@@ -28562,16 +28545,11 @@ var init_daily_brief2 = __esm({
|
|
|
28562
28545
|
|
|
28563
28546
|
// src/command-os/connectors/brief-panels.ts
|
|
28564
28547
|
function briefPanelDeps(input) {
|
|
28565
|
-
return {
|
|
28566
|
-
stripe: () => stripePanel(input),
|
|
28567
|
-
slack: () => slackPanel(input),
|
|
28568
|
-
calendar: () => calendarPanel(input),
|
|
28569
|
-
chat: (id, name) => chatPanel(id, name)(input)
|
|
28570
|
-
};
|
|
28548
|
+
return { stripe: () => stripePanel(input), slack: () => slackPanel(input), calendar: () => calendarPanel(input) };
|
|
28571
28549
|
}
|
|
28572
28550
|
function readTimes() {
|
|
28573
28551
|
try {
|
|
28574
|
-
return TimesSchema.parse(JSON.parse(
|
|
28552
|
+
return TimesSchema.parse(JSON.parse(fs21.readFileSync(timesPath(), "utf8")));
|
|
28575
28553
|
} catch {
|
|
28576
28554
|
return {};
|
|
28577
28555
|
}
|
|
@@ -28580,13 +28558,13 @@ function remember(id, readAt) {
|
|
|
28580
28558
|
const times = readTimes();
|
|
28581
28559
|
times[id] = readAt;
|
|
28582
28560
|
ensureAppDataDir();
|
|
28583
|
-
const tmp = `${timesPath()}.${(0,
|
|
28561
|
+
const tmp = `${timesPath()}.${(0, import_node_crypto6.randomUUID)()}.tmp`;
|
|
28584
28562
|
try {
|
|
28585
|
-
|
|
28586
|
-
|
|
28563
|
+
fs21.writeFileSync(tmp, JSON.stringify(times) + "\n", { mode: 384, flag: "wx" });
|
|
28564
|
+
fs21.renameSync(tmp, timesPath());
|
|
28587
28565
|
} finally {
|
|
28588
28566
|
try {
|
|
28589
|
-
|
|
28567
|
+
fs21.unlinkSync(tmp);
|
|
28590
28568
|
} catch {
|
|
28591
28569
|
}
|
|
28592
28570
|
}
|
|
@@ -28630,39 +28608,20 @@ function money2(rows2) {
|
|
|
28630
28608
|
}
|
|
28631
28609
|
return { count: rows2.length, totals: [...totals2].map(([currency, amount]) => ({ currency, amount })), unpriced };
|
|
28632
28610
|
}
|
|
28633
|
-
function chatPanel(id, name) {
|
|
28634
|
-
return async (_deps) => {
|
|
28635
|
-
try {
|
|
28636
|
-
const record = readBriefAnswers()[id];
|
|
28637
|
-
return {
|
|
28638
|
-
id,
|
|
28639
|
-
name,
|
|
28640
|
-
ok: Boolean(record),
|
|
28641
|
-
state: record ? "read" : "not-recorded",
|
|
28642
|
-
readAt: record?.readAt ?? null,
|
|
28643
|
-
why: record ? "Filled by your last chat brief" : "Nothing recorded yet. Ask Claude: run my morning brief",
|
|
28644
|
-
rows: record?.rows ?? [],
|
|
28645
|
-
needsYou: []
|
|
28646
|
-
};
|
|
28647
|
-
} catch {
|
|
28648
|
-
return failure(id, name, "The saved chat brief could not be read; ask Claude to record it again.");
|
|
28649
|
-
}
|
|
28650
|
-
};
|
|
28651
|
-
}
|
|
28652
28611
|
async function readBriefPanels(deps) {
|
|
28653
28612
|
return Promise.all(CONNECTOR_REGISTRY.flatMap((entry) => entry.panel ? [entry.panel(briefPanelDeps(deps))] : []));
|
|
28654
28613
|
}
|
|
28655
|
-
var
|
|
28614
|
+
var fs21, path19, import_node_crypto6, import_zod57, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, amountText, stripePanel, slackPanel, calendarPanel;
|
|
28656
28615
|
var init_brief_panels = __esm({
|
|
28657
28616
|
"src/command-os/connectors/brief-panels.ts"() {
|
|
28658
28617
|
"use strict";
|
|
28659
28618
|
init_brief_deadline();
|
|
28660
|
-
|
|
28661
|
-
|
|
28662
|
-
|
|
28619
|
+
fs21 = __toESM(require("node:fs"));
|
|
28620
|
+
path19 = __toESM(require("node:path"));
|
|
28621
|
+
import_node_crypto6 = require("node:crypto");
|
|
28663
28622
|
import_zod57 = require("zod");
|
|
28664
28623
|
init_credentials_store();
|
|
28665
|
-
|
|
28624
|
+
init_brief_row();
|
|
28666
28625
|
init_registry();
|
|
28667
28626
|
init_store2();
|
|
28668
28627
|
init_handlers();
|
|
@@ -28697,7 +28656,7 @@ var init_brief_panels = __esm({
|
|
|
28697
28656
|
money: import_zod57.z.object({ owed: MoneySchema, renewals: MoneySchema, summary: SummarySchema.optional() }).optional()
|
|
28698
28657
|
});
|
|
28699
28658
|
TimesSchema = import_zod57.z.record(import_zod57.z.string(), import_zod57.z.iso.datetime({ offset: true }));
|
|
28700
|
-
timesPath = () =>
|
|
28659
|
+
timesPath = () => path19.join(appDataDir(), "brief-read-times.json");
|
|
28701
28660
|
NotConnected = class extends Error {
|
|
28702
28661
|
constructor() {
|
|
28703
28662
|
super("not connected");
|
|
@@ -29099,7 +29058,7 @@ var require_package = __commonJS({
|
|
|
29099
29058
|
"package.json"(exports2, module2) {
|
|
29100
29059
|
module2.exports = {
|
|
29101
29060
|
name: "@elitedcs/ghl-mcp",
|
|
29102
|
-
version: "3.84.
|
|
29061
|
+
version: "3.84.8",
|
|
29103
29062
|
mcpName: "io.github.drjerryrelth/ghl-command",
|
|
29104
29063
|
description: "GoHighLevel MCP Server for Claude. 250 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
|
|
29105
29064
|
main: "dist/index.js",
|
|
@@ -29198,17 +29157,17 @@ var require_package = __commonJS({
|
|
|
29198
29157
|
});
|
|
29199
29158
|
|
|
29200
29159
|
// src/package-version.ts
|
|
29201
|
-
var
|
|
29160
|
+
var fs22, path20, bundledPkg, pkg;
|
|
29202
29161
|
var init_package_version = __esm({
|
|
29203
29162
|
"src/package-version.ts"() {
|
|
29204
29163
|
"use strict";
|
|
29205
|
-
|
|
29206
|
-
|
|
29164
|
+
fs22 = __toESM(require("fs"));
|
|
29165
|
+
path20 = __toESM(require("path"));
|
|
29207
29166
|
bundledPkg = require_package();
|
|
29208
29167
|
pkg = (() => {
|
|
29209
29168
|
try {
|
|
29210
29169
|
const onDisk = JSON.parse(
|
|
29211
|
-
|
|
29170
|
+
fs22.readFileSync(path20.resolve(__dirname, "..", "package.json"), "utf8")
|
|
29212
29171
|
);
|
|
29213
29172
|
if (typeof onDisk.version === "string" && onDisk.version.length > 0) {
|
|
29214
29173
|
return { version: onDisk.version };
|
|
@@ -31470,20 +31429,20 @@ function clientHeadline(items) {
|
|
|
31470
31429
|
const live = items.filter((i) => i.state === "in-place").length;
|
|
31471
31430
|
const notYet = items.filter((i) => i.state === "not-yet").length;
|
|
31472
31431
|
const waiting = items.filter((i) => i.state === "waiting").length;
|
|
31473
|
-
const
|
|
31432
|
+
const plural2 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
31474
31433
|
if (!items.length) return { headline: "Your account has not been checked yet", sub: "As soon as we run the first check, this page fills in on its own." };
|
|
31475
31434
|
if (!notYet && !waiting) {
|
|
31476
31435
|
return {
|
|
31477
31436
|
headline: "Everything we set out to build is live",
|
|
31478
|
-
sub: `All ${
|
|
31437
|
+
sub: `All ${plural2(live, "item was", "items were")} read back from your live account, not assumed. This page re-checks itself and updates here, so it is always current.`
|
|
31479
31438
|
};
|
|
31480
31439
|
}
|
|
31481
31440
|
const parts = [];
|
|
31482
|
-
if (live) parts.push(`${
|
|
31483
|
-
if (notYet) parts.push(`${
|
|
31484
|
-
if (waiting) parts.push(`${
|
|
31441
|
+
if (live) parts.push(`${plural2(live, "thing is", "things are")} live in your account`);
|
|
31442
|
+
if (notYet) parts.push(`${plural2(notYet, "is", "are")} built but not confirmed yet`);
|
|
31443
|
+
if (waiting) parts.push(`${plural2(waiting, "is", "are")} waiting on someone else`);
|
|
31485
31444
|
return {
|
|
31486
|
-
headline: notYet ? `${
|
|
31445
|
+
headline: notYet ? `${plural2(notYet, "item", "items")} still to confirm` : `${plural2(waiting, "item is", "items are")} waiting on someone else`,
|
|
31487
31446
|
sub: `${parts.join(", ").replace(/, ([^,]*)$/, " and $1")}. Everything below was read back from your live account.`
|
|
31488
31447
|
};
|
|
31489
31448
|
}
|
|
@@ -31797,11 +31756,11 @@ var init_client_status = __esm({
|
|
|
31797
31756
|
|
|
31798
31757
|
// src/command-os/client-status-placement.ts
|
|
31799
31758
|
function statusStorePath(base = appDataDir()) {
|
|
31800
|
-
return
|
|
31759
|
+
return path21.join(base, "client-status.json");
|
|
31801
31760
|
}
|
|
31802
31761
|
function readStatusStore(base) {
|
|
31803
31762
|
try {
|
|
31804
|
-
const raw = JSON.parse(
|
|
31763
|
+
const raw = JSON.parse(fs23.readFileSync(statusStorePath(base), "utf8"));
|
|
31805
31764
|
if (raw && raw.v === 1 && raw.clients) return raw;
|
|
31806
31765
|
} catch {
|
|
31807
31766
|
}
|
|
@@ -31809,8 +31768,8 @@ function readStatusStore(base) {
|
|
|
31809
31768
|
}
|
|
31810
31769
|
function writeStatusStore(store, base) {
|
|
31811
31770
|
const file2 = statusStorePath(base);
|
|
31812
|
-
|
|
31813
|
-
|
|
31771
|
+
fs23.mkdirSync(path21.dirname(file2), { recursive: true, mode: 448 });
|
|
31772
|
+
fs23.writeFileSync(file2, JSON.stringify(store, null, 2), { mode: 384 });
|
|
31814
31773
|
}
|
|
31815
31774
|
function readStatusRecord(locationId2, base) {
|
|
31816
31775
|
return readStatusStore(base).clients[locationId2] ?? { locationId: locationId2 };
|
|
@@ -31842,9 +31801,9 @@ function describePlacement(placement) {
|
|
|
31842
31801
|
return `A real page inside the client's own GoHighLevel account, at /${placement.slug ?? STATUS_SLUG}${placement.publicUrl ? ` \u2014 ${placement.publicUrl}` : ""}. Every re-check replaces that one page; it never adds a second copy.`;
|
|
31843
31802
|
}
|
|
31844
31803
|
if (placement.kind === "agency-site") {
|
|
31845
|
-
return `A file on your own site: ${
|
|
31804
|
+
return `A file on your own site: ${path21.join(placement.directory, placement.fileName ?? "status.html")}${placement.publicUrl ? `, which you publish at ${placement.publicUrl}` : ""}. The same file is rewritten every time, so the address never changes.`;
|
|
31846
31805
|
}
|
|
31847
|
-
return `A page written to ${
|
|
31806
|
+
return `A page written to ${path21.join(placement.directory, placement.fileName ?? "status.html")} for you to publish as an Artifact from your own Claude${placement.publicUrl ? `, currently at ${placement.publicUrl}` : ""}.`;
|
|
31848
31807
|
}
|
|
31849
31808
|
function placementReadiness(placement) {
|
|
31850
31809
|
if (!placement) return ["Choose where this client's page should live: their own GoHighLevel account, your own site, or an Artifact from your own Claude."];
|
|
@@ -31903,12 +31862,12 @@ function artifactPublishSteps(filePath, existingUrl) {
|
|
|
31903
31862
|
"Send the client the link once. From then on they just revisit it."
|
|
31904
31863
|
];
|
|
31905
31864
|
}
|
|
31906
|
-
var
|
|
31865
|
+
var fs23, path21, STATUS_SLUG, OUR_HOSTS;
|
|
31907
31866
|
var init_client_status_placement = __esm({
|
|
31908
31867
|
"src/command-os/client-status-placement.ts"() {
|
|
31909
31868
|
"use strict";
|
|
31910
|
-
|
|
31911
|
-
|
|
31869
|
+
fs23 = __toESM(require("node:fs"));
|
|
31870
|
+
path21 = __toESM(require("node:path"));
|
|
31912
31871
|
init_credentials_store();
|
|
31913
31872
|
init_client_status();
|
|
31914
31873
|
STATUS_SLUG = "status";
|
|
@@ -31928,7 +31887,7 @@ var init_client_status_placement = __esm({
|
|
|
31928
31887
|
// src/build-contract.ts
|
|
31929
31888
|
function contentHash(value) {
|
|
31930
31889
|
const canonical2 = (v) => Array.isArray(v) ? v.map(canonical2) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => [k, canonical2(v[k])])) : v;
|
|
31931
|
-
return (0,
|
|
31890
|
+
return (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(canonical2(value)) ?? "null").digest("hex");
|
|
31932
31891
|
}
|
|
31933
31892
|
function assertBuildApproval(approval, inputHash) {
|
|
31934
31893
|
if (!approval?.at) throw new Error("Approve building before running Build.");
|
|
@@ -31987,9 +31946,9 @@ function intakeInput(answers, industry, questions) {
|
|
|
31987
31946
|
}
|
|
31988
31947
|
function shortenPlanLine(text2, limit = 360) {
|
|
31989
31948
|
if (text2.length <= limit) return text2;
|
|
31990
|
-
const
|
|
31949
|
+
const tokens2 = text2.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
|
|
31991
31950
|
let end = "";
|
|
31992
|
-
for (const token2 of
|
|
31951
|
+
for (const token2 of tokens2) {
|
|
31993
31952
|
const next = end ? `${end} ${token2}` : token2;
|
|
31994
31953
|
if (next.length > limit - 1) {
|
|
31995
31954
|
if (!end) return token2.length === text2.length ? text2 : `${token2}\u2026`;
|
|
@@ -32057,8 +32016,8 @@ function executablePlanLines(saved) {
|
|
|
32057
32016
|
const refs = [...names.keys()].sort((a, b) => b.length - a.length);
|
|
32058
32017
|
const refPattern = refs.length ? new RegExp(`(?<![\\w./:@])(?:${refs.map(escapeRE).join("|")})(?![\\w]|\\.[\\w])`, "g") : void 0;
|
|
32059
32018
|
const missingRefPattern = /(?<![\w./:@])(user|pipeline|stage|field|tag|workflow|form|calendar|email_template|sms_template|email|sms|cv|handoff|funnel)\.[a-z0-9_]+(?![\w]|\.[\w])/g;
|
|
32060
|
-
const
|
|
32061
|
-
const renderRefs = (s) =>
|
|
32019
|
+
const resolve13 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
|
|
32020
|
+
const renderRefs = (s) => resolve13(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : s);
|
|
32062
32021
|
const mergeLabel = (field) => {
|
|
32063
32022
|
const [group, ...parts] = field.trim().split(".");
|
|
32064
32023
|
const key = parts.join(".");
|
|
@@ -32103,11 +32062,11 @@ function executablePlanLines(saved) {
|
|
|
32103
32062
|
for (const w of preview?.workflows ?? []) for (const m of [...w.manual ?? [], ...w.needsContent ?? []]) lines.push(`Unresolved: ${w.name || names.get(w.ref) || "Unnamed workflow"}. ${m.reason}`);
|
|
32104
32063
|
return lines.map(sentence);
|
|
32105
32064
|
}
|
|
32106
|
-
var
|
|
32065
|
+
var import_node_crypto7, import_node_fs3, import_node_path3, INTAKE_CHANGED;
|
|
32107
32066
|
var init_build_contract = __esm({
|
|
32108
32067
|
"src/build-contract.ts"() {
|
|
32109
32068
|
"use strict";
|
|
32110
|
-
|
|
32069
|
+
import_node_crypto7 = require("node:crypto");
|
|
32111
32070
|
import_node_fs3 = require("node:fs");
|
|
32112
32071
|
import_node_path3 = require("node:path");
|
|
32113
32072
|
init_credentials_store();
|
|
@@ -32367,30 +32326,30 @@ var init_client_status_page = __esm({
|
|
|
32367
32326
|
|
|
32368
32327
|
// src/command-os/client-status-publish.ts
|
|
32369
32328
|
function handoffSnapshotDir(base = appDataDir()) {
|
|
32370
|
-
return
|
|
32329
|
+
return path22.join(base, "handoffs");
|
|
32371
32330
|
}
|
|
32372
32331
|
function handoffSnapshotPath(locationId2, base) {
|
|
32373
|
-
return
|
|
32332
|
+
return path22.join(handoffSnapshotDir(base), `${locationId2}-handoff.json`);
|
|
32374
32333
|
}
|
|
32375
32334
|
function saveHandoffSnapshot(saved, base) {
|
|
32376
32335
|
const file2 = handoffSnapshotPath(saved.locationId, base);
|
|
32377
|
-
|
|
32378
|
-
|
|
32336
|
+
fs24.mkdirSync(path22.dirname(file2), { recursive: true, mode: 448 });
|
|
32337
|
+
fs24.writeFileSync(file2, JSON.stringify(saved, null, 2), { mode: 384 });
|
|
32379
32338
|
return file2;
|
|
32380
32339
|
}
|
|
32381
32340
|
function readHandoffSnapshot(locationId2, base) {
|
|
32382
32341
|
try {
|
|
32383
|
-
return JSON.parse(
|
|
32342
|
+
return JSON.parse(fs24.readFileSync(handoffSnapshotPath(locationId2, base), "utf8"));
|
|
32384
32343
|
} catch {
|
|
32385
32344
|
return null;
|
|
32386
32345
|
}
|
|
32387
32346
|
}
|
|
32388
32347
|
function localCopyPath(locationId2, base = appDataDir()) {
|
|
32389
|
-
return
|
|
32348
|
+
return path22.join(base, "client-status", `${locationId2}-status.html`);
|
|
32390
32349
|
}
|
|
32391
32350
|
function readVerifyOutcome(locationId2, base = appDataDir()) {
|
|
32392
32351
|
try {
|
|
32393
|
-
const st = JSON.parse(
|
|
32352
|
+
const st = JSON.parse(fs24.readFileSync(path22.join(base, "cockpit-state.json"), "utf8"));
|
|
32394
32353
|
const o = st.outcomes?.[`${locationId2}:4`];
|
|
32395
32354
|
if (!o) return null;
|
|
32396
32355
|
const src = o.plain ?? { summary: o.summary, issues: o.issues };
|
|
@@ -32400,9 +32359,9 @@ function readVerifyOutcome(locationId2, base = appDataDir()) {
|
|
|
32400
32359
|
}
|
|
32401
32360
|
}
|
|
32402
32361
|
function writeFileFor(dir, fileName, html2) {
|
|
32403
|
-
|
|
32404
|
-
const file2 =
|
|
32405
|
-
|
|
32362
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
32363
|
+
const file2 = path22.join(dir, fileName);
|
|
32364
|
+
fs24.writeFileSync(file2, html2);
|
|
32406
32365
|
return file2;
|
|
32407
32366
|
}
|
|
32408
32367
|
async function refreshClientStatus(opts) {
|
|
@@ -32438,7 +32397,7 @@ async function refreshClientStatus(opts) {
|
|
|
32438
32397
|
record.preparedSentences = input.items.map((i) => i.sentence);
|
|
32439
32398
|
if (!opts.dryRun) saveStatusRecord({ ...record, locationId: opts.locationId }, opts.base);
|
|
32440
32399
|
const localPath = localCopyPath(opts.locationId, opts.base);
|
|
32441
|
-
if (!opts.dryRun) writeFileFor(
|
|
32400
|
+
if (!opts.dryRun) writeFileFor(path22.dirname(localPath), path22.basename(localPath), html2);
|
|
32442
32401
|
const placement = record.placement;
|
|
32443
32402
|
const missing = placementReadiness(placement);
|
|
32444
32403
|
if (!placement || missing.length) {
|
|
@@ -32485,7 +32444,7 @@ async function publishTo(placement, input, html2, opts) {
|
|
|
32485
32444
|
if (placement.kind === "agency-site") {
|
|
32486
32445
|
assertNotOurHost(placement.publicUrl, "The address you chose for this page");
|
|
32487
32446
|
const fileName = placement.fileName ?? "status.html";
|
|
32488
|
-
const destinationPath = opts.dryRun ?
|
|
32447
|
+
const destinationPath = opts.dryRun ? path22.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
|
|
32489
32448
|
return {
|
|
32490
32449
|
...placement.publicUrl && { url: placement.publicUrl },
|
|
32491
32450
|
destinationPath,
|
|
@@ -32499,7 +32458,7 @@ async function publishTo(placement, input, html2, opts) {
|
|
|
32499
32458
|
const safety = artifactSafety(html2);
|
|
32500
32459
|
if (!safety.ok) throw new Error(safety.problems.join(" "));
|
|
32501
32460
|
const fileName = placement.fileName ?? "status.html";
|
|
32502
|
-
const destinationPath = opts.dryRun ?
|
|
32461
|
+
const destinationPath = opts.dryRun ? path22.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
|
|
32503
32462
|
assertNotOurHost(placement.publicUrl, "The Artifact link saved for this client");
|
|
32504
32463
|
return {
|
|
32505
32464
|
...placement.publicUrl && { url: placement.publicUrl },
|
|
@@ -32540,13 +32499,13 @@ async function refreshAfterVerify(locationId2, accountName, emit2, base, approve
|
|
|
32540
32499
|
emit2(`The client's status page was not updated: ${e instanceof Error ? e.message : String(e)}`);
|
|
32541
32500
|
}
|
|
32542
32501
|
}
|
|
32543
|
-
var
|
|
32502
|
+
var fs24, path22;
|
|
32544
32503
|
var init_client_status_publish = __esm({
|
|
32545
32504
|
"src/command-os/client-status-publish.ts"() {
|
|
32546
32505
|
"use strict";
|
|
32547
32506
|
init_publication_proof();
|
|
32548
|
-
|
|
32549
|
-
|
|
32507
|
+
fs24 = __toESM(require("node:fs"));
|
|
32508
|
+
path22 = __toESM(require("node:path"));
|
|
32550
32509
|
init_credentials_store();
|
|
32551
32510
|
init_branding();
|
|
32552
32511
|
init_client_status();
|
|
@@ -32625,11 +32584,11 @@ var init_handoff_snapshot = __esm({
|
|
|
32625
32584
|
// src/build-obligations.ts
|
|
32626
32585
|
function fileFor(id) {
|
|
32627
32586
|
if (!/^[a-zA-Z0-9_-]+$/.test(id)) throw new Error("Choose a registered client.");
|
|
32628
|
-
return
|
|
32587
|
+
return path23.join(appDataDir(), "build-obligations", id + ".json");
|
|
32629
32588
|
}
|
|
32630
32589
|
function readBuildObligations(id) {
|
|
32631
32590
|
try {
|
|
32632
|
-
const data = JSON.parse(
|
|
32591
|
+
const data = JSON.parse(fs25.readFileSync(fileFor(id), "utf8"));
|
|
32633
32592
|
return Array.isArray(data) ? data : [];
|
|
32634
32593
|
} catch (e) {
|
|
32635
32594
|
if (e.code === "ENOENT") return [];
|
|
@@ -32640,9 +32599,9 @@ function saveBuildObligations(id, items) {
|
|
|
32640
32599
|
const merged = new Map(readBuildObligations(id).map((m) => [`${m.type}:${m.ref}`, m]));
|
|
32641
32600
|
for (const item of items) merged.set(`${item.type}:${item.ref}`, item);
|
|
32642
32601
|
const file2 = fileFor(id);
|
|
32643
|
-
|
|
32644
|
-
|
|
32645
|
-
|
|
32602
|
+
fs25.mkdirSync(path23.dirname(file2), { recursive: true });
|
|
32603
|
+
fs25.writeFileSync(file2 + ".tmp", JSON.stringify([...merged.values()], null, 2), { mode: 384 });
|
|
32604
|
+
fs25.renameSync(file2 + ".tmp", file2);
|
|
32646
32605
|
}
|
|
32647
32606
|
function planObjectRefs(plan) {
|
|
32648
32607
|
const refs = /* @__PURE__ */ new Set();
|
|
@@ -32667,16 +32626,16 @@ function reconcileBuildObligations(id, checks, currentPlan) {
|
|
|
32667
32626
|
item.type === "workflow-manual" && Array.isArray(workflows) && workflows.some((workflow) => item.ref.startsWith(`${workflow.name}:`) && /^\d+$/.test(item.ref.slice(workflow.name.length + 1)));
|
|
32668
32627
|
const remaining = readBuildObligations(id).filter((item) => stillPlanned(item) && !checks.some((c) => c.ref === item.ref && c.type === item.type && c.ok === true && typeof c.evidence === "string" && c.evidence.trim()));
|
|
32669
32628
|
const file2 = fileFor(id);
|
|
32670
|
-
|
|
32671
|
-
|
|
32672
|
-
|
|
32629
|
+
fs25.mkdirSync(path23.dirname(file2), { recursive: true });
|
|
32630
|
+
fs25.writeFileSync(file2 + ".tmp", JSON.stringify(remaining, null, 2), { mode: 384 });
|
|
32631
|
+
fs25.renameSync(file2 + ".tmp", file2);
|
|
32673
32632
|
}
|
|
32674
|
-
var
|
|
32633
|
+
var fs25, path23;
|
|
32675
32634
|
var init_build_obligations = __esm({
|
|
32676
32635
|
"src/build-obligations.ts"() {
|
|
32677
32636
|
"use strict";
|
|
32678
|
-
|
|
32679
|
-
|
|
32637
|
+
fs25 = __toESM(require("node:fs"));
|
|
32638
|
+
path23 = __toESM(require("node:path"));
|
|
32680
32639
|
init_credentials_store();
|
|
32681
32640
|
}
|
|
32682
32641
|
});
|
|
@@ -32686,7 +32645,7 @@ function engineRepair(version) {
|
|
|
32686
32645
|
return !version ? CLAUDE_REPAIR_MESSAGES[0] : !meetsFloor(version) ? CLAUDE_REPAIR_MESSAGES[1] : CLAUDE_REPAIR_MESSAGES[2];
|
|
32687
32646
|
}
|
|
32688
32647
|
function keyFingerprint(key) {
|
|
32689
|
-
return (0,
|
|
32648
|
+
return (0, import_node_crypto8.createHash)("sha256").update(key).digest("hex");
|
|
32690
32649
|
}
|
|
32691
32650
|
async function clientPreflight(registry2, locationId2) {
|
|
32692
32651
|
const lines = [];
|
|
@@ -32753,7 +32712,7 @@ async function agencyConnectionReady(registry2, companyId) {
|
|
|
32753
32712
|
return false;
|
|
32754
32713
|
}
|
|
32755
32714
|
}
|
|
32756
|
-
var
|
|
32715
|
+
var import_node_crypto8, CLAUDE_REPAIR_MESSAGES;
|
|
32757
32716
|
var init_client_preflight = __esm({
|
|
32758
32717
|
"src/client-preflight.ts"() {
|
|
32759
32718
|
"use strict";
|
|
@@ -32763,7 +32722,7 @@ var init_client_preflight = __esm({
|
|
|
32763
32722
|
init_stage_runner();
|
|
32764
32723
|
init_tool_filter();
|
|
32765
32724
|
init_credentials_store();
|
|
32766
|
-
|
|
32725
|
+
import_node_crypto8 = require("node:crypto");
|
|
32767
32726
|
CLAUDE_REPAIR_MESSAGES = [
|
|
32768
32727
|
"Install Claude Code from Your agency before reading this source.",
|
|
32769
32728
|
"Update Claude Code to version 2.1.0 or newer from Your agency.",
|
|
@@ -32870,7 +32829,7 @@ function startDesignRun(request, opts = {}) {
|
|
|
32870
32829
|
const checked = validateRunRequest(request);
|
|
32871
32830
|
if (!checked.ok) throw new Error(checked.error);
|
|
32872
32831
|
request = checked.value;
|
|
32873
|
-
const id = (0,
|
|
32832
|
+
const id = (0, import_node_crypto9.randomUUID)();
|
|
32874
32833
|
const outDir = (0, import_node_path5.join)(designRunsDir(), id);
|
|
32875
32834
|
(0, import_node_fs5.mkdirSync)(outDir, { recursive: true });
|
|
32876
32835
|
const job = { id, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), outDir, request, tail: [] };
|
|
@@ -32937,14 +32896,14 @@ function cancelDesignRun(id) {
|
|
|
32937
32896
|
}
|
|
32938
32897
|
return true;
|
|
32939
32898
|
}
|
|
32940
|
-
var import_node_child_process3, import_node_fs5, import_node_path5,
|
|
32899
|
+
var import_node_child_process3, import_node_fs5, import_node_path5, import_node_crypto9, SLUG2, MODEL, JOINED_FLAG, jobs, children;
|
|
32941
32900
|
var init_run_jobs = __esm({
|
|
32942
32901
|
"src/command-os/design/run-jobs.ts"() {
|
|
32943
32902
|
"use strict";
|
|
32944
32903
|
import_node_child_process3 = require("node:child_process");
|
|
32945
32904
|
import_node_fs5 = require("node:fs");
|
|
32946
32905
|
import_node_path5 = require("node:path");
|
|
32947
|
-
|
|
32906
|
+
import_node_crypto9 = require("node:crypto");
|
|
32948
32907
|
init_credentials_store();
|
|
32949
32908
|
init_schema();
|
|
32950
32909
|
init_brief_validation();
|
|
@@ -32972,20 +32931,20 @@ var init_run_expectations = __esm({
|
|
|
32972
32931
|
|
|
32973
32932
|
// src/run-cost.ts
|
|
32974
32933
|
function runCostPath() {
|
|
32975
|
-
return
|
|
32934
|
+
return path24.join(appDataDir(), "command-os-run-cost.jsonl");
|
|
32976
32935
|
}
|
|
32977
32936
|
function recordRunCost(rec, file2 = runCostPath()) {
|
|
32978
|
-
|
|
32979
|
-
|
|
32937
|
+
fs26.mkdirSync(path24.dirname(file2), { recursive: true });
|
|
32938
|
+
fs26.appendFileSync(file2, `${JSON.stringify(rec)}
|
|
32980
32939
|
`, { mode: 384 });
|
|
32981
32940
|
try {
|
|
32982
|
-
|
|
32941
|
+
fs26.chmodSync(file2, 384);
|
|
32983
32942
|
} catch {
|
|
32984
32943
|
}
|
|
32985
32944
|
}
|
|
32986
32945
|
function readRunCosts(file2 = runCostPath()) {
|
|
32987
32946
|
try {
|
|
32988
|
-
return
|
|
32947
|
+
return fs26.readFileSync(file2, "utf8").split("\n").filter((l) => l.trim()).map((l) => {
|
|
32989
32948
|
try {
|
|
32990
32949
|
return JSON.parse(l);
|
|
32991
32950
|
} catch {
|
|
@@ -33016,12 +32975,12 @@ function costLines(o) {
|
|
|
33016
32975
|
if (o.engineCheckRan && o.engineCheckCostUsd === void 0) out.push("Claude readiness check: cost not reported.");
|
|
33017
32976
|
return out;
|
|
33018
32977
|
}
|
|
33019
|
-
var
|
|
32978
|
+
var fs26, path24;
|
|
33020
32979
|
var init_run_cost = __esm({
|
|
33021
32980
|
"src/run-cost.ts"() {
|
|
33022
32981
|
"use strict";
|
|
33023
|
-
|
|
33024
|
-
|
|
32982
|
+
fs26 = __toESM(require("fs"));
|
|
32983
|
+
path24 = __toESM(require("path"));
|
|
33025
32984
|
init_credentials_store();
|
|
33026
32985
|
}
|
|
33027
32986
|
});
|
|
@@ -33029,7 +32988,7 @@ var init_run_cost = __esm({
|
|
|
33029
32988
|
// src/claude-call.ts
|
|
33030
32989
|
function callClaudeRecorded(bin, argv, context, onCost = () => {
|
|
33031
32990
|
}) {
|
|
33032
|
-
return new Promise((
|
|
32991
|
+
return new Promise((resolve13, reject) => {
|
|
33033
32992
|
let text2 = "";
|
|
33034
32993
|
let buffer = "";
|
|
33035
32994
|
let stderr = "";
|
|
@@ -33048,7 +33007,7 @@ function callClaudeRecorded(bin, argv, context, onCost = () => {
|
|
|
33048
33007
|
onCost("The cost record could not be saved.");
|
|
33049
33008
|
}
|
|
33050
33009
|
onCost(`${context.kind}: ${facts.costUsd === void 0 ? "cost not reported" : formatUsd(facts.costUsd)}.`);
|
|
33051
|
-
if (ok)
|
|
33010
|
+
if (ok) resolve13(text2);
|
|
33052
33011
|
else reject(new Error("Check your Claude authentication in Your agency before reading this source."));
|
|
33053
33012
|
};
|
|
33054
33013
|
const consume = (line3) => {
|
|
@@ -33157,9 +33116,9 @@ function unknownKeys(layer, against) {
|
|
|
33157
33116
|
return referenced.filter((k) => !have.has(k));
|
|
33158
33117
|
}
|
|
33159
33118
|
function installQuestionSet(opts = {}) {
|
|
33160
|
-
return composeQuestionSet(opts).map((
|
|
33161
|
-
const base = INTAKE_QUESTIONS.find((b) => b.key ===
|
|
33162
|
-
return base && base.label !==
|
|
33119
|
+
return composeQuestionSet(opts).map((q3) => {
|
|
33120
|
+
const base = INTAKE_QUESTIONS.find((b) => b.key === q3.key);
|
|
33121
|
+
return base && base.label !== q3.label ? { ...q3, label: base.label } : q3;
|
|
33163
33122
|
});
|
|
33164
33123
|
}
|
|
33165
33124
|
function validatePacks() {
|
|
@@ -33466,7 +33425,7 @@ function parsePricePoints(text2) {
|
|
|
33466
33425
|
const out = [];
|
|
33467
33426
|
const segments = text2.split(/\r?\n|;|\.\s+/);
|
|
33468
33427
|
const ENTRY = /^(.*?)\s*[-–—:|]?\s*\$?\s*([\d,]+(?:\.\d+)?)\s*(?:[-–—]\s*\$?\s*([\d,]+(?:\.\d+)?))?\s*(\/\s*[a-z]+|(?:per|an?|each)\s+[a-z]+)?\s*$/i;
|
|
33469
|
-
const
|
|
33428
|
+
const clean2 = (s) => s.replace(/[\s.,:;|–—-]+$/, "").trim();
|
|
33470
33429
|
const num3 = (s) => s.replace(/,/g, "");
|
|
33471
33430
|
const recur = (s) => s ? "/" + s.replace(/^\/|^(?:per|an?|each)\b/i, "").trim().toLowerCase() : "";
|
|
33472
33431
|
for (const segment of segments) {
|
|
@@ -33474,7 +33433,7 @@ function parsePricePoints(text2) {
|
|
|
33474
33433
|
if (!t) continue;
|
|
33475
33434
|
const m = t.match(ENTRY);
|
|
33476
33435
|
if (m && m[2]) {
|
|
33477
|
-
const name =
|
|
33436
|
+
const name = clean2(m[1]);
|
|
33478
33437
|
const price = (m[3] ? `${num3(m[2])}-${num3(m[3])}` : num3(m[2])) + recur(m[4]);
|
|
33479
33438
|
out.push({ name: name || t, price });
|
|
33480
33439
|
} else {
|
|
@@ -33604,11 +33563,11 @@ function splitOnMarkers(line3) {
|
|
|
33604
33563
|
}
|
|
33605
33564
|
function splitOnAnd(entry) {
|
|
33606
33565
|
if (countDurations(entry) < 2) return [entry];
|
|
33607
|
-
const
|
|
33608
|
-
if (
|
|
33566
|
+
const tokens2 = entry.split(/,?\s+and\s+/i);
|
|
33567
|
+
if (tokens2.length < 2) return [entry];
|
|
33609
33568
|
const out = [];
|
|
33610
33569
|
let acc = "";
|
|
33611
|
-
for (const tok of
|
|
33570
|
+
for (const tok of tokens2) {
|
|
33612
33571
|
if (acc && countDurations(acc) > 0 && countDurations(tok) > 0) {
|
|
33613
33572
|
out.push(acc);
|
|
33614
33573
|
acc = tok;
|
|
@@ -33860,10 +33819,10 @@ function buildFieldMapFromFormFields(fields, questions = INTAKE_QUESTIONS) {
|
|
|
33860
33819
|
if (fieldKey && id) byFieldKey.set(fieldKey.toLowerCase(), id);
|
|
33861
33820
|
}
|
|
33862
33821
|
const map = {};
|
|
33863
|
-
for (const
|
|
33864
|
-
if (
|
|
33865
|
-
const id = byFieldKey.get(expectedFieldKey(
|
|
33866
|
-
if (id) map[
|
|
33822
|
+
for (const q3 of questions) {
|
|
33823
|
+
if (q3.field.kind !== "custom") continue;
|
|
33824
|
+
const id = byFieldKey.get(expectedFieldKey(q3).toLowerCase());
|
|
33825
|
+
if (id) map[q3.key] = id;
|
|
33867
33826
|
}
|
|
33868
33827
|
return map;
|
|
33869
33828
|
}
|
|
@@ -33925,11 +33884,11 @@ function normalizeSubmissionToBrief(opts) {
|
|
|
33925
33884
|
goal: {}
|
|
33926
33885
|
};
|
|
33927
33886
|
const warnings = [];
|
|
33928
|
-
const rawFor = (
|
|
33929
|
-
const rawKey =
|
|
33887
|
+
const rawFor = (q3) => {
|
|
33888
|
+
const rawKey = q3.field.kind === "standard" ? q3.field.tag : opts.fieldMap[q3.key];
|
|
33930
33889
|
return rawKey ? opts.others[rawKey] : void 0;
|
|
33931
33890
|
};
|
|
33932
|
-
const staffQ = questions.find((
|
|
33891
|
+
const staffQ = questions.find((q3) => q3.briefKind === "staffList" && q3.briefPath);
|
|
33933
33892
|
let staff;
|
|
33934
33893
|
if (staffQ) {
|
|
33935
33894
|
const s = firstString(rawFor(staffQ));
|
|
@@ -33940,22 +33899,22 @@ function normalizeSubmissionToBrief(opts) {
|
|
|
33940
33899
|
}
|
|
33941
33900
|
}
|
|
33942
33901
|
const staffNames = (staff ?? []).map((m) => m.name);
|
|
33943
|
-
for (const
|
|
33944
|
-
if (!
|
|
33902
|
+
for (const q3 of questions) {
|
|
33903
|
+
if (!q3.briefPath || q3.briefKind === "identity") continue;
|
|
33945
33904
|
let value;
|
|
33946
|
-
if (
|
|
33905
|
+
if (q3 === staffQ) {
|
|
33947
33906
|
value = staff;
|
|
33948
|
-
} else if (
|
|
33949
|
-
const s = firstString(rawFor(
|
|
33907
|
+
} else if (q3.briefKind === "calendarList") {
|
|
33908
|
+
const s = firstString(rawFor(q3));
|
|
33950
33909
|
if (s) {
|
|
33951
33910
|
const r = parseCalendarLines(s, staffNames);
|
|
33952
33911
|
warnings.push(...r.warnings);
|
|
33953
33912
|
value = r.calendars.length ? r.calendars : void 0;
|
|
33954
33913
|
}
|
|
33955
33914
|
} else {
|
|
33956
|
-
value = coerceValue(rawFor(
|
|
33915
|
+
value = coerceValue(rawFor(q3), q3.briefKind, warnings);
|
|
33957
33916
|
}
|
|
33958
|
-
setPath(brief,
|
|
33917
|
+
setPath(brief, q3.briefPath, value);
|
|
33959
33918
|
}
|
|
33960
33919
|
distillAssignments(brief, staffNames, warnings);
|
|
33961
33920
|
if (warnings.length) brief.warnings = warnings;
|
|
@@ -34624,17 +34583,17 @@ function planFromPreset(input) {
|
|
|
34624
34583
|
}
|
|
34625
34584
|
function presetsRoot() {
|
|
34626
34585
|
const candidates = [
|
|
34627
|
-
|
|
34586
|
+
path25.join(__dirname, "..", "skills", "blueprint", "presets"),
|
|
34628
34587
|
// dist/ → package root
|
|
34629
|
-
|
|
34588
|
+
path25.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
|
|
34630
34589
|
// src/intake-to-build in dev
|
|
34631
|
-
|
|
34590
|
+
path25.join(process.cwd(), "skills", "blueprint", "presets")
|
|
34632
34591
|
];
|
|
34633
|
-
return candidates.find((c) =>
|
|
34592
|
+
return candidates.find((c) => fs27.existsSync(c)) ?? null;
|
|
34634
34593
|
}
|
|
34635
34594
|
function readPresetFile(dir, base) {
|
|
34636
34595
|
try {
|
|
34637
|
-
const raw = JSON.parse(
|
|
34596
|
+
const raw = JSON.parse(fs27.readFileSync(path25.join(dir, `${base}.preset.json`), "utf8"));
|
|
34638
34597
|
if (raw && typeof raw === "object" && typeof raw.presetId === "string" && raw.skeleton) return raw;
|
|
34639
34598
|
return null;
|
|
34640
34599
|
} catch {
|
|
@@ -34646,7 +34605,7 @@ function loadPresetChoice(requested, briefPreset) {
|
|
|
34646
34605
|
if (!dir) {
|
|
34647
34606
|
return { ok: false, error: "The preset library is not installed with this copy of the tool (skills/blueprint/presets is missing) \u2014 reinstall, or author the plan by hand." };
|
|
34648
34607
|
}
|
|
34649
|
-
const bases =
|
|
34608
|
+
const bases = fs27.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
|
|
34650
34609
|
const tryMatch = (want) => {
|
|
34651
34610
|
const norm6 = slugify2(want);
|
|
34652
34611
|
for (const base of bases) {
|
|
@@ -34677,12 +34636,12 @@ function loadPresetChoice(requested, briefPreset) {
|
|
|
34677
34636
|
if (generic) return { ok: true, preset: generic, file: "generic-client.preset.json", note: "Using the generic preset." };
|
|
34678
34637
|
return { ok: false, error: "No usable preset file was found in the library." };
|
|
34679
34638
|
}
|
|
34680
|
-
var
|
|
34639
|
+
var fs27, path25, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
|
|
34681
34640
|
var init_plan_from_preset = __esm({
|
|
34682
34641
|
"src/intake-to-build/plan-from-preset.ts"() {
|
|
34683
34642
|
"use strict";
|
|
34684
|
-
|
|
34685
|
-
|
|
34643
|
+
fs27 = __toESM(require("fs"));
|
|
34644
|
+
path25 = __toESM(require("path"));
|
|
34686
34645
|
init_plan();
|
|
34687
34646
|
init_brief2();
|
|
34688
34647
|
init_normalizer();
|
|
@@ -34865,7 +34824,7 @@ function compute(ans, skip, asm) {
|
|
|
34865
34824
|
var monthly = F.reduce(function(s, f) {
|
|
34866
34825
|
return s + f.monthly;
|
|
34867
34826
|
}, 0);
|
|
34868
|
-
var
|
|
34827
|
+
var stop2 = monthly * a("recovery");
|
|
34869
34828
|
var oneoff = null;
|
|
34870
34829
|
if (core && raw("dormant_count") !== null && a("reactivation") > 0) {
|
|
34871
34830
|
oneoff = raw("dormant_count") * a("reactivation") * c * v;
|
|
@@ -34898,8 +34857,8 @@ function compute(ans, skip, asm) {
|
|
|
34898
34857
|
core,
|
|
34899
34858
|
monthly,
|
|
34900
34859
|
annual: monthly * 12,
|
|
34901
|
-
stop,
|
|
34902
|
-
annualStop:
|
|
34860
|
+
stop: stop2,
|
|
34861
|
+
annualStop: stop2 * 12,
|
|
34903
34862
|
oneoff,
|
|
34904
34863
|
hrs,
|
|
34905
34864
|
hrsWk,
|
|
@@ -35001,8 +34960,8 @@ var init_engine = __esm({
|
|
|
35001
34960
|
show_rate: "How many booked appointments actually happen"
|
|
35002
34961
|
};
|
|
35003
34962
|
UNIT = {};
|
|
35004
|
-
Q.forEach(function(
|
|
35005
|
-
UNIT[
|
|
34963
|
+
Q.forEach(function(q3) {
|
|
34964
|
+
UNIT[q3[0]] = q3[4];
|
|
35006
34965
|
});
|
|
35007
34966
|
}
|
|
35008
34967
|
});
|
|
@@ -35021,7 +34980,7 @@ function stageFor(opts) {
|
|
|
35021
34980
|
function tagFor(stage) {
|
|
35022
34981
|
return "assessment:" + stage.toLowerCase().replace(/\s+/g, "-");
|
|
35023
34982
|
}
|
|
35024
|
-
function renderHuman(state, computed,
|
|
34983
|
+
function renderHuman(state, computed, q3, today2) {
|
|
35025
34984
|
const lines = [];
|
|
35026
34985
|
lines.push(`${NOTE_TITLE} \u2014 ${state.biz || "unnamed"}${state.trade ? " \u2014 " + state.trade : ""} \u2014 ${today2}`);
|
|
35027
34986
|
if (computed) {
|
|
@@ -35036,8 +34995,8 @@ function renderHuman(state, computed, q2, today2) {
|
|
|
35036
34995
|
const byArea = /* @__PURE__ */ new Map();
|
|
35037
34996
|
const ids = [.../* @__PURE__ */ new Set([...Object.keys(state.ans), ...Object.keys(state.skip)])];
|
|
35038
34997
|
for (const id of ids) {
|
|
35039
|
-
const ask =
|
|
35040
|
-
const area =
|
|
34998
|
+
const ask = q3.ask(id);
|
|
34999
|
+
const area = q3.area(id);
|
|
35041
35000
|
if (!ask || !area) continue;
|
|
35042
35001
|
const skipped = state.skip[id] === true;
|
|
35043
35002
|
const raw = state.ans[id];
|
|
@@ -35049,7 +35008,7 @@ function renderHuman(state, computed, q2, today2) {
|
|
|
35049
35008
|
${value}`);
|
|
35050
35009
|
byArea.set(area, bucket);
|
|
35051
35010
|
}
|
|
35052
|
-
for (const area of
|
|
35011
|
+
for (const area of q3.areaOrder()) {
|
|
35053
35012
|
const bucket = byArea.get(area);
|
|
35054
35013
|
if (!bucket || bucket.length === 0) continue;
|
|
35055
35014
|
lines.push("");
|
|
@@ -35061,7 +35020,7 @@ function renderHuman(state, computed, q2, today2) {
|
|
|
35061
35020
|
lines.push("");
|
|
35062
35021
|
lines.push("ASSUMPTIONS THE OPERATOR SET");
|
|
35063
35022
|
for (const [k, v] of asm) {
|
|
35064
|
-
const label =
|
|
35023
|
+
const label = q3.assumption?.(k) ?? k.replace(/_/g, " ");
|
|
35065
35024
|
lines.push(` ${label}: ${Math.round((Number(v) || 0) * 100)}%${Number(v) > 0 ? "" : " (set to zero \u2014 its finding was dropped, not estimated)"}`);
|
|
35066
35025
|
}
|
|
35067
35026
|
}
|
|
@@ -35082,9 +35041,23 @@ function freshSalt() {
|
|
|
35082
35041
|
function sanitiseHuman(text2) {
|
|
35083
35042
|
return text2.split(MACHINE_MARKER).join("--- (marker text removed) ---");
|
|
35084
35043
|
}
|
|
35085
|
-
function
|
|
35086
|
-
const
|
|
35087
|
-
|
|
35044
|
+
function describeSource(src) {
|
|
35045
|
+
const title2 = src.title?.trim() ? ` "${src.title.trim()}"` : "";
|
|
35046
|
+
const by = src.fetchedBy === "claude" ? ", fetched by Claude" : src.fetchedBy === "upload" ? ", uploaded as a file" : "";
|
|
35047
|
+
return `${src.recorder} recording${title2}, recorded ${src.recordedAt}${by}`;
|
|
35048
|
+
}
|
|
35049
|
+
function buildNote(state, computed, q3, today2, source, confirmedByOwner) {
|
|
35050
|
+
const confirmed = confirmedByOwner?.length ? [...confirmedByOwner] : void 0;
|
|
35051
|
+
const payload = JSON.stringify({ v: 1, savedAt: today2, state, computed, ...source ? { source } : {}, ...confirmed ? { confirmedByOwner: confirmed } : {} });
|
|
35052
|
+
const human = renderHuman(state, computed, q3, today2);
|
|
35053
|
+
const lines = [
|
|
35054
|
+
...source ? [`Source: ${describeSource(source)}`] : [],
|
|
35055
|
+
...confirmed ? [`Confirmed by the owner, not heard word for word: ${confirmed.map((id) => q3.ask(id) ?? id).join("; ")}`] : []
|
|
35056
|
+
];
|
|
35057
|
+
const withSource = lines.length ? human.replace(/\n/, `
|
|
35058
|
+
${lines.join("\n")}
|
|
35059
|
+
`) : human;
|
|
35060
|
+
return `${sanitiseHuman(withSource)}
|
|
35088
35061
|
|
|
35089
35062
|
${MACHINE_MARKER}
|
|
35090
35063
|
${payload}`;
|
|
@@ -35109,7 +35082,9 @@ function parseNote(body) {
|
|
|
35109
35082
|
if (!s.ans || typeof s.ans !== "object") return null;
|
|
35110
35083
|
return {
|
|
35111
35084
|
state: { biz: s.biz, trade: s.trade, ans: s.ans, skip: s.skip ?? {}, asm: s.asm ?? {}, ...s.client && typeof s.client === "object" ? { client: s.client } : {} },
|
|
35112
|
-
computed: p.computed ?? null
|
|
35085
|
+
computed: p.computed ?? null,
|
|
35086
|
+
// Which answers the owner confirmed rather than were heard (BUG-2026-09-24-04), so the next save can carry the mark.
|
|
35087
|
+
...Array.isArray(p.confirmedByOwner) ? { confirmedByOwner: p.confirmedByOwner.filter((x) => typeof x === "string") } : {}
|
|
35113
35088
|
};
|
|
35114
35089
|
}
|
|
35115
35090
|
function buildContact(state, stage, contact) {
|
|
@@ -35186,7 +35161,16 @@ function parseNotes(bodies) {
|
|
|
35186
35161
|
${parts.map((p) => p.text).join("")}`);
|
|
35187
35162
|
}
|
|
35188
35163
|
function pickLatest(notes) {
|
|
35189
|
-
|
|
35164
|
+
for (const parsed2 of latestFirst(notes)) if (parsed2) return parsed2;
|
|
35165
|
+
return null;
|
|
35166
|
+
}
|
|
35167
|
+
function pickLatestStrict(notes) {
|
|
35168
|
+
const all = latestFirst(notes);
|
|
35169
|
+
if (all.length === 0) return { record: null, clean: true };
|
|
35170
|
+
return { record: all[0], clean: all[0] !== null };
|
|
35171
|
+
}
|
|
35172
|
+
function latestFirst(notes) {
|
|
35173
|
+
if (!Array.isArray(notes)) return [];
|
|
35190
35174
|
const groups = /* @__PURE__ */ new Map();
|
|
35191
35175
|
notes.forEach((note, i) => {
|
|
35192
35176
|
const body = typeof note?.body === "string" ? note.body : null;
|
|
@@ -35204,18 +35188,21 @@ function pickLatest(notes) {
|
|
|
35204
35188
|
groups.set(key, g);
|
|
35205
35189
|
});
|
|
35206
35190
|
const ordered = [...groups.values()].sort((a, b) => a.when === b.when ? 0 : a.when > b.when ? -1 : 1);
|
|
35191
|
+
const out = [];
|
|
35207
35192
|
for (const g of ordered) {
|
|
35208
35193
|
const newestPerK = /* @__PURE__ */ new Map();
|
|
35209
35194
|
for (const p of g.parts) {
|
|
35210
35195
|
const seen = newestPerK.get(p.k);
|
|
35211
35196
|
if (!seen || p.when > seen.when) newestPerK.set(p.k, p);
|
|
35212
35197
|
}
|
|
35213
|
-
if (newestPerK.size !== g.n)
|
|
35198
|
+
if (newestPerK.size !== g.n) {
|
|
35199
|
+
out.push(null);
|
|
35200
|
+
continue;
|
|
35201
|
+
}
|
|
35214
35202
|
const bodies = [...newestPerK.values()].sort((a, b) => a.k - b.k).map((p) => p.body);
|
|
35215
|
-
|
|
35216
|
-
if (parsed2) return parsed2;
|
|
35203
|
+
out.push(bodies.length === 1 ? parseNote(bodies[0]) : parseNotes(bodies));
|
|
35217
35204
|
}
|
|
35218
|
-
return
|
|
35205
|
+
return out;
|
|
35219
35206
|
}
|
|
35220
35207
|
var PIPELINE_NAME, NOTE_TITLE, MACHINE_MARKER, STAGES, MARKER_LINE, NOTE_LIMIT;
|
|
35221
35208
|
var init_store3 = __esm({
|
|
@@ -35291,13 +35278,13 @@ function count(value) {
|
|
|
35291
35278
|
const n = Number(value);
|
|
35292
35279
|
return Number.isSafeInteger(n) ? n : void 0;
|
|
35293
35280
|
}
|
|
35294
|
-
function matchesQuestionChoices(
|
|
35295
|
-
const fixed =
|
|
35281
|
+
function matchesQuestionChoices(q3, value) {
|
|
35282
|
+
const fixed = q3.options !== void 0 || q3.field.kind === "custom" && ["SINGLE_OPTIONS", "MULTIPLE_OPTIONS", "CHECKBOX"].includes(q3.field.dataType);
|
|
35296
35283
|
if (!fixed) return true;
|
|
35297
35284
|
if (Array.isArray(value)) {
|
|
35298
|
-
return
|
|
35285
|
+
return q3.field.kind === "custom" && ["MULTIPLE_OPTIONS", "CHECKBOX"].includes(q3.field.dataType) && value.every((choice) => q3.options?.includes(choice) === true);
|
|
35299
35286
|
}
|
|
35300
|
-
return
|
|
35287
|
+
return q3.options?.includes(value) === true;
|
|
35301
35288
|
}
|
|
35302
35289
|
function mapAssessmentToIntake(s, questions, contact) {
|
|
35303
35290
|
const values = {
|
|
@@ -35323,14 +35310,14 @@ function mapAssessmentToIntake(s, questions, contact) {
|
|
|
35323
35310
|
const answers = {};
|
|
35324
35311
|
const filled = [], leftForMember = [];
|
|
35325
35312
|
for (const mapping of ASSESSMENT_INTAKE_MAPPING) {
|
|
35326
|
-
const
|
|
35313
|
+
const q3 = questions.find((q4) => q4.key === mapping.target);
|
|
35327
35314
|
const value = values[mapping.target]?.trim();
|
|
35328
|
-
if (!
|
|
35329
|
-
leftForMember.push({ key: mapping.target, question:
|
|
35315
|
+
if (!q3 || !value || !matchesQuestionChoices(q3, value)) {
|
|
35316
|
+
leftForMember.push({ key: mapping.target, question: q3?.label ?? mapping.target, reason: !q3 ? "This question is not in this intake." : !value ? "The assessment does not give this answer." : "The answer does not match one of this intake\u2019s choices." });
|
|
35330
35317
|
continue;
|
|
35331
35318
|
}
|
|
35332
|
-
answers[
|
|
35333
|
-
filled.push(
|
|
35319
|
+
answers[q3.label] = q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS" ? [value] : value;
|
|
35320
|
+
filled.push(q3.label);
|
|
35334
35321
|
}
|
|
35335
35322
|
return { answers, filled, leftForMember };
|
|
35336
35323
|
}
|
|
@@ -35348,19 +35335,19 @@ function fillFromSnapshot(state, locationId2, questions) {
|
|
|
35348
35335
|
const result = mapAssessmentToIntake(snapshot.assessment, questions, snapshot.contact);
|
|
35349
35336
|
const answers = { ...state.intakeInputs?.[locationId2]?.answers, ...state.intakeDrafts?.[locationId2]?.answers };
|
|
35350
35337
|
for (const [label, value] of Object.entries(result.answers)) {
|
|
35351
|
-
const
|
|
35352
|
-
const previous = answers[label] ?? answers[
|
|
35338
|
+
const q3 = questions.find((q4) => q4.label === label);
|
|
35339
|
+
const previous = answers[label] ?? answers[q3.key];
|
|
35353
35340
|
let merged = value;
|
|
35354
|
-
if ((
|
|
35341
|
+
if ((q3.key === "anything_else" || q3.key === "price_points") && previous && !String(previous).includes(String(value))) merged = `${previous}
|
|
35355
35342
|
${value}`;
|
|
35356
|
-
else if ((
|
|
35357
|
-
if (!matchesQuestionChoices(
|
|
35343
|
+
else if ((q3.key === "anything_else" || q3.key === "price_points") && previous) merged = previous;
|
|
35344
|
+
if (!matchesQuestionChoices(q3, merged)) {
|
|
35358
35345
|
result.filled = result.filled.filter((question) => question !== label);
|
|
35359
|
-
result.leftForMember.push({ key:
|
|
35346
|
+
result.leftForMember.push({ key: q3.key, question: label, reason: "The combined answer does not match this intake\u2019s choices. Your saved answer was left unchanged." });
|
|
35360
35347
|
continue;
|
|
35361
35348
|
}
|
|
35362
35349
|
answers[label] = merged;
|
|
35363
|
-
if (
|
|
35350
|
+
if (q3.key !== label) delete answers[q3.key];
|
|
35364
35351
|
}
|
|
35365
35352
|
return { state: { ...state, intakeDrafts: { ...state.intakeDrafts, [locationId2]: { answers, savedAt: (/* @__PURE__ */ new Date()).toISOString() } } }, filled: result.filled, leftForMember: result.leftForMember };
|
|
35366
35353
|
}
|
|
@@ -35448,10 +35435,10 @@ var init_attachment = __esm({
|
|
|
35448
35435
|
async function pickIntakeContact(client, locationId2, fieldIdToLabel, knownContactId, formName) {
|
|
35449
35436
|
const ids = /* @__PURE__ */ new Set();
|
|
35450
35437
|
if (knownContactId) ids.add(knownContactId);
|
|
35451
|
-
for (const
|
|
35438
|
+
for (const q3 of [...INTAKE_TAGS, ...formName ? [formName] : []]) {
|
|
35452
35439
|
try {
|
|
35453
|
-
const found = await client.get("/contacts/", { params: { locationId: locationId2, query:
|
|
35454
|
-
for (const c of found.contacts ?? []) if ((c.tags ?? []).some((t) => INTAKE_TAGS.includes(t)) ||
|
|
35440
|
+
const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q3, limit: 20 } });
|
|
35441
|
+
for (const c of found.contacts ?? []) if ((c.tags ?? []).some((t) => INTAKE_TAGS.includes(t)) || q3 === formName) ids.add(c.id);
|
|
35455
35442
|
} catch {
|
|
35456
35443
|
}
|
|
35457
35444
|
}
|
|
@@ -35534,17 +35521,17 @@ function buildHeaderField(title2, index) {
|
|
|
35534
35521
|
weight: 400
|
|
35535
35522
|
};
|
|
35536
35523
|
}
|
|
35537
|
-
function buildStandardFormField(
|
|
35538
|
-
if (
|
|
35539
|
-
const tag =
|
|
35524
|
+
function buildStandardFormField(q3) {
|
|
35525
|
+
if (q3.field.kind !== "standard") throw new Error(`buildStandardFormField: ${q3.key} is not standard`);
|
|
35526
|
+
const tag = q3.field.tag;
|
|
35540
35527
|
const isEmail = tag === "email";
|
|
35541
35528
|
const isPhone = tag === "phone";
|
|
35542
35529
|
const base = {
|
|
35543
35530
|
fieldWidthPercentage: 100,
|
|
35544
35531
|
hiddenFieldQueryKey: tag,
|
|
35545
|
-
label:
|
|
35546
|
-
placeholder:
|
|
35547
|
-
required:
|
|
35532
|
+
label: q3.label,
|
|
35533
|
+
placeholder: q3.placeholder ?? q3.label,
|
|
35534
|
+
required: q3.required,
|
|
35548
35535
|
standard: true,
|
|
35549
35536
|
tag,
|
|
35550
35537
|
type: isEmail ? "email" : "text",
|
|
@@ -35553,7 +35540,7 @@ function buildStandardFormField(q2) {
|
|
|
35553
35540
|
if (isPhone) base.enableCountryPicker = false;
|
|
35554
35541
|
return base;
|
|
35555
35542
|
}
|
|
35556
|
-
function buildCustomFormField(
|
|
35543
|
+
function buildCustomFormField(q3, record, locationId2, position2) {
|
|
35557
35544
|
const type = formFieldType(record.dataType);
|
|
35558
35545
|
const field = {
|
|
35559
35546
|
Id: record.id,
|
|
@@ -35561,7 +35548,7 @@ function buildCustomFormField(q2, record, locationId2, position2) {
|
|
|
35561
35548
|
tag: record.id,
|
|
35562
35549
|
active: false,
|
|
35563
35550
|
allowCustomOption: false,
|
|
35564
|
-
customFieldLabel:
|
|
35551
|
+
customFieldLabel: q3.label,
|
|
35565
35552
|
dataType: record.dataType,
|
|
35566
35553
|
dateAdded: record.dateAdded ?? "",
|
|
35567
35554
|
description: "",
|
|
@@ -35571,19 +35558,19 @@ function buildCustomFormField(q2, record, locationId2, position2) {
|
|
|
35571
35558
|
fieldWidthPercentage: 100,
|
|
35572
35559
|
fieldsCount: 0,
|
|
35573
35560
|
hiddenFieldQueryKey: slug(record.name),
|
|
35574
|
-
label:
|
|
35561
|
+
label: q3.label,
|
|
35575
35562
|
locationId: locationId2,
|
|
35576
35563
|
model: record.model ?? "contact",
|
|
35577
35564
|
name: record.name,
|
|
35578
35565
|
parentId: record.parentId ?? "",
|
|
35579
|
-
placeholder:
|
|
35566
|
+
placeholder: q3.placeholder ?? "",
|
|
35580
35567
|
position: position2,
|
|
35581
|
-
required:
|
|
35568
|
+
required: q3.required,
|
|
35582
35569
|
showInForms: true,
|
|
35583
35570
|
standard: false,
|
|
35584
35571
|
type
|
|
35585
35572
|
};
|
|
35586
|
-
const options = record.picklistOptions ??
|
|
35573
|
+
const options = record.picklistOptions ?? q3.options ?? void 0;
|
|
35587
35574
|
if (type === "single_options" || type === "multiple_options" || type === "checkbox") {
|
|
35588
35575
|
field.picklistOptions = options ? [...options] : [];
|
|
35589
35576
|
}
|
|
@@ -35632,17 +35619,17 @@ function buildIntakeFormData(opts) {
|
|
|
35632
35619
|
let lastSection = "";
|
|
35633
35620
|
let headerIndex = 0;
|
|
35634
35621
|
let position2 = 0;
|
|
35635
|
-
for (const
|
|
35636
|
-
if (
|
|
35637
|
-
fields.push(buildHeaderField(
|
|
35638
|
-
lastSection =
|
|
35622
|
+
for (const q3 of questions) {
|
|
35623
|
+
if (q3.section !== lastSection) {
|
|
35624
|
+
fields.push(buildHeaderField(q3.sectionLabel, headerIndex++));
|
|
35625
|
+
lastSection = q3.section;
|
|
35639
35626
|
}
|
|
35640
|
-
if (
|
|
35641
|
-
fields.push(buildStandardFormField(
|
|
35627
|
+
if (q3.field.kind === "standard") {
|
|
35628
|
+
fields.push(buildStandardFormField(q3));
|
|
35642
35629
|
} else {
|
|
35643
|
-
const record = opts.resolved.get(
|
|
35630
|
+
const record = opts.resolved.get(q3.key);
|
|
35644
35631
|
if (!record) continue;
|
|
35645
|
-
fields.push(buildCustomFormField(
|
|
35632
|
+
fields.push(buildCustomFormField(q3, record, opts.locationId, position2 += 50));
|
|
35646
35633
|
}
|
|
35647
35634
|
}
|
|
35648
35635
|
fields.push(buildSubmitButton("Submit"));
|
|
@@ -35672,33 +35659,67 @@ var init_form_template = __esm({
|
|
|
35672
35659
|
}
|
|
35673
35660
|
});
|
|
35674
35661
|
|
|
35662
|
+
// src/command-os/assessment/words.ts
|
|
35663
|
+
function tokens(s) {
|
|
35664
|
+
return String(s ?? "").toLowerCase().replace(/[‘’ʼ]/g, "'").replace(/[–—]/g, "-").replace(/%/g, " percent ").split(/[^a-z0-9.']+/).map((w) => w.replace(/^\.+|\.+$/g, "")).filter(Boolean);
|
|
35665
|
+
}
|
|
35666
|
+
function numericValue(value) {
|
|
35667
|
+
const v = String(value ?? "").trim().toLowerCase().replace(/[$,\s]/g, "");
|
|
35668
|
+
const m = /^(-?\d+(?:\.\d+)?)(%|k|m)?$/.exec(v);
|
|
35669
|
+
if (!m) return null;
|
|
35670
|
+
const n = Number(m[1]) * (m[2] === "k" ? 1e3 : m[2] === "m" ? 1e6 : 1);
|
|
35671
|
+
return { n, percent: m[2] === "%" };
|
|
35672
|
+
}
|
|
35673
|
+
var init_words = __esm({
|
|
35674
|
+
"src/command-os/assessment/words.ts"() {
|
|
35675
|
+
"use strict";
|
|
35676
|
+
}
|
|
35677
|
+
});
|
|
35678
|
+
|
|
35675
35679
|
// src/command-os/assessment/transcript.ts
|
|
35676
35680
|
function loose(s) {
|
|
35677
35681
|
return String(s ?? "").replace(/[‘’ʼ]/g, "'").replace(/[“”]/g, '"').replace(/[–—]/g, "-").replace(/\s+/g, " ").trim().toLowerCase();
|
|
35678
35682
|
}
|
|
35679
|
-
function
|
|
35680
|
-
|
|
35681
|
-
}
|
|
35682
|
-
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
if (v.length === 0) return false;
|
|
35686
|
-
if (q2.includes(v)) return true;
|
|
35687
|
-
const dv = digits(value);
|
|
35688
|
-
if (dv.length > 0 && digits(quote).includes(dv)) return true;
|
|
35689
|
-
return false;
|
|
35683
|
+
function valueHeard(value, quote, unit) {
|
|
35684
|
+
const v = String(value ?? "").trim();
|
|
35685
|
+
if (unit ? NUMERIC_UNITS.has(unit) : numericValue(v) !== null) return { ok: false, because: NUMBER_HELD };
|
|
35686
|
+
const tv = tokens(v).join(" ");
|
|
35687
|
+
if (tv && ` ${tokens(quote).join(" ")} `.includes(` ${tv} `)) return { ok: true };
|
|
35688
|
+
return { ok: false, because: `"${v}" is not in what they said, so it was worked out rather than heard. Confirm it before it counts.` };
|
|
35690
35689
|
}
|
|
35691
|
-
function citationCheck(material, value, quote) {
|
|
35690
|
+
function citationCheck(material, value, quote, unit) {
|
|
35692
35691
|
const v = String(value ?? "").trim();
|
|
35693
|
-
const
|
|
35692
|
+
const q3 = String(quote ?? "").trim();
|
|
35694
35693
|
if (v === "") return { verdict: "refused", because: "no value was given." };
|
|
35695
|
-
|
|
35696
|
-
if (
|
|
35694
|
+
const said = quoteCheck(material, q3);
|
|
35695
|
+
if (said.verdict === "refused") return said;
|
|
35696
|
+
const heard = valueHeard(v, q3, unit);
|
|
35697
|
+
if (!heard.ok) return { verdict: "held", because: heard.because };
|
|
35698
|
+
return { verdict: "ok" };
|
|
35699
|
+
}
|
|
35700
|
+
function quoteCheck(material, quote, opts = {}) {
|
|
35701
|
+
const q3 = String(quote ?? "").trim();
|
|
35702
|
+
if (q3 === "") return { verdict: "refused", because: "no quote was given, so there is nothing tying this answer to what they said." };
|
|
35703
|
+
if (q3.length > MAX_QUOTE) return { verdict: "refused", because: `the quote is ${q3.length} characters. A quote that long is a summary, not a citation.` };
|
|
35704
|
+
const needle = loose(q3);
|
|
35705
|
+
const words = needle.split(" ").filter((w) => WORDCHAR.test(w)).length;
|
|
35706
|
+
if (opts.minWords && words < opts.minWords) {
|
|
35707
|
+
return { verdict: "refused", because: `the quote is ${words === 1 ? "one word" : `${words} words`}. It needs at least ${opts.minWords} words to show they actually said it.` };
|
|
35708
|
+
}
|
|
35697
35709
|
const hay = loose(material);
|
|
35698
|
-
if (hay.length === 0 || !hay
|
|
35699
|
-
if (!valueIsInQuote(v, q2)) return { verdict: "held", because: `"${v}" is not in what they said, so it was worked out rather than heard. Confirm it before it counts.` };
|
|
35710
|
+
if (hay.length === 0 || !containsAsWords(hay, needle)) return { verdict: "refused", because: "that quote does not appear in the transcript." };
|
|
35700
35711
|
return { verdict: "ok" };
|
|
35701
35712
|
}
|
|
35713
|
+
function containsAsWords(hay, needle) {
|
|
35714
|
+
const checkStart = WORDCHAR.test(needle[0] ?? "");
|
|
35715
|
+
const checkEnd = WORDCHAR.test(needle[needle.length - 1] ?? "");
|
|
35716
|
+
for (let i = hay.indexOf(needle); i !== -1; i = hay.indexOf(needle, i + 1)) {
|
|
35717
|
+
const before = i > 0 ? hay[i - 1] : "";
|
|
35718
|
+
const after = hay[i + needle.length] ?? "";
|
|
35719
|
+
if ((!checkStart || !WORDCHAR.test(before)) && (!checkEnd || !WORDCHAR.test(after))) return true;
|
|
35720
|
+
}
|
|
35721
|
+
return false;
|
|
35722
|
+
}
|
|
35702
35723
|
function fitsItsUnit(value, unit) {
|
|
35703
35724
|
if (unit === void 0 || unit === "text") return true;
|
|
35704
35725
|
const n = parseTyped(value, unit);
|
|
@@ -35734,7 +35755,7 @@ function gateExtraction(transcript, proposed, questions) {
|
|
|
35734
35755
|
rejected.push({ ...item, because: "the same question was answered twice; only the first was taken." });
|
|
35735
35756
|
continue;
|
|
35736
35757
|
}
|
|
35737
|
-
const cite = citationCheck(transcript, item.value, item.quote);
|
|
35758
|
+
const cite = citationCheck(transcript, item.value, item.quote, questions.get(item.questionId));
|
|
35738
35759
|
if (cite.verdict === "refused") {
|
|
35739
35760
|
rejected.push({ ...item, because: cite.because });
|
|
35740
35761
|
continue;
|
|
@@ -35760,12 +35781,16 @@ function gateExtraction(transcript, proposed, questions) {
|
|
|
35760
35781
|
function mergeAccepted(ans, result) {
|
|
35761
35782
|
return { ...ans, ...result.patch };
|
|
35762
35783
|
}
|
|
35763
|
-
var MAX_QUOTE;
|
|
35784
|
+
var NUMERIC_UNITS, NUMBER_HELD, MAX_QUOTE, WORDCHAR;
|
|
35764
35785
|
var init_transcript = __esm({
|
|
35765
35786
|
"src/command-os/assessment/transcript.ts"() {
|
|
35766
35787
|
"use strict";
|
|
35767
35788
|
init_engine();
|
|
35789
|
+
init_words();
|
|
35790
|
+
NUMERIC_UNITS = /* @__PURE__ */ new Set(["count", "currency", "rate", "minutes", "hours"]);
|
|
35791
|
+
NUMBER_HELD = "a number: confirm it. Numbers always wait for the owner's OK, because no word check can tell a figure that answers the question from one said in passing.";
|
|
35768
35792
|
MAX_QUOTE = 400;
|
|
35793
|
+
WORDCHAR = /[\p{L}\p{N}]/u;
|
|
35769
35794
|
}
|
|
35770
35795
|
});
|
|
35771
35796
|
|
|
@@ -35842,8 +35867,8 @@ async function readIntakeSnapshot(client, locationId2, sentMarker) {
|
|
|
35842
35867
|
if (!submissions) {
|
|
35843
35868
|
try {
|
|
35844
35869
|
const seen = /* @__PURE__ */ new Map();
|
|
35845
|
-
for (const
|
|
35846
|
-
const found = await client.get("/contacts/", { params: { locationId: locationId2, query:
|
|
35870
|
+
for (const q3 of [INTAKE_CONTACT_TAG, INTAKE_FORM_NAME2]) {
|
|
35871
|
+
const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q3, limit: 20 } });
|
|
35847
35872
|
for (const c of found.contacts ?? []) seen.set(c.id, c);
|
|
35848
35873
|
}
|
|
35849
35874
|
const isFormSubmission = (c) => c.source === INTAKE_FORM_NAME2 || (c.attributions ?? []).some((a) => a.mediumId === form.id || a.medium === "form");
|
|
@@ -35977,15 +36002,15 @@ async function runPrefill(call2, labels, material) {
|
|
|
35977
36002
|
}
|
|
35978
36003
|
function validatePrefillAnswers(questions, proposed) {
|
|
35979
36004
|
const result = {};
|
|
35980
|
-
for (const
|
|
35981
|
-
const raw = proposed[
|
|
36005
|
+
for (const q3 of questions) {
|
|
36006
|
+
const raw = proposed[q3.label];
|
|
35982
36007
|
if (raw === void 0) continue;
|
|
35983
36008
|
const values = (Array.isArray(raw) ? raw : [raw]).map((x) => x.trim()).filter(Boolean);
|
|
35984
36009
|
if (!values.length) continue;
|
|
35985
|
-
if (
|
|
35986
|
-
if (values.some((v) => !
|
|
35987
|
-
result[
|
|
35988
|
-
} else if (values.length === 1) result[
|
|
36010
|
+
if (q3.options?.length) {
|
|
36011
|
+
if (values.some((v) => !q3.options.includes(v)) || !q3.multi && values.length !== 1) continue;
|
|
36012
|
+
result[q3.label] = q3.multi ? [...new Set(values)] : values[0];
|
|
36013
|
+
} else if (values.length === 1) result[q3.label] = values[0];
|
|
35989
36014
|
}
|
|
35990
36015
|
return result;
|
|
35991
36016
|
}
|
|
@@ -36005,13 +36030,13 @@ var init_client_intake = __esm({
|
|
|
36005
36030
|
|
|
36006
36031
|
// src/plan-store.ts
|
|
36007
36032
|
function plansDir(base = appDataDir()) {
|
|
36008
|
-
return
|
|
36033
|
+
return path26.join(base, "plans");
|
|
36009
36034
|
}
|
|
36010
36035
|
function savePlanRecord(rec, base) {
|
|
36011
36036
|
const dir = plansDir(base);
|
|
36012
|
-
|
|
36013
|
-
const file2 =
|
|
36014
|
-
|
|
36037
|
+
fs28.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
36038
|
+
const file2 = path26.join(dir, `${rec.locationId}.json`);
|
|
36039
|
+
fs28.writeFileSync(file2, JSON.stringify({ ...rec, contentHash: contentHash(rec.plan), inputHash: rec.inputHash ?? process.env.GHL_COMMAND_INTAKE_HASH, dependencies: rec.dependencies ?? planDependencies(rec.plan) }, null, 2), { mode: 384 });
|
|
36015
36040
|
return file2;
|
|
36016
36041
|
}
|
|
36017
36042
|
function savedPlanMatchesScope(saved, current) {
|
|
@@ -36026,7 +36051,7 @@ function savedPlanMatchesScope(saved, current) {
|
|
|
36026
36051
|
}
|
|
36027
36052
|
function readSavedPlan(locationId2, base) {
|
|
36028
36053
|
try {
|
|
36029
|
-
return JSON.parse(
|
|
36054
|
+
return JSON.parse(fs28.readFileSync(path26.join(plansDir(base), `${locationId2}.json`), "utf8"));
|
|
36030
36055
|
} catch {
|
|
36031
36056
|
return null;
|
|
36032
36057
|
}
|
|
@@ -36049,12 +36074,12 @@ function planSummary(rec) {
|
|
|
36049
36074
|
if (wfs.length) parts.push(`workflows: ${wfs.map((w) => `${w.name} (${Array.isArray(w.actions) ? w.actions.length : "?"} actions)`).join("; ")}`);
|
|
36050
36075
|
return parts.join("\n") || "(plan has no recognisable sections)";
|
|
36051
36076
|
}
|
|
36052
|
-
var
|
|
36077
|
+
var fs28, path26;
|
|
36053
36078
|
var init_plan_store = __esm({
|
|
36054
36079
|
"src/plan-store.ts"() {
|
|
36055
36080
|
"use strict";
|
|
36056
|
-
|
|
36057
|
-
|
|
36081
|
+
fs28 = __toESM(require("fs"));
|
|
36082
|
+
path26 = __toESM(require("path"));
|
|
36058
36083
|
init_credentials_store();
|
|
36059
36084
|
init_build_contract();
|
|
36060
36085
|
}
|
|
@@ -37717,7 +37742,7 @@ function launchCommand(port = 7300) {
|
|
|
37717
37742
|
return `npx -y @elitedcs/ghl-mcp@latest dashboard --port=${port}`;
|
|
37718
37743
|
}
|
|
37719
37744
|
function installingEntry() {
|
|
37720
|
-
return
|
|
37745
|
+
return path27.join(__dirname, "index.js");
|
|
37721
37746
|
}
|
|
37722
37747
|
function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
37723
37748
|
const cmd = launchCommand(port);
|
|
@@ -37725,12 +37750,12 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
37725
37750
|
const systemApps = "/Applications";
|
|
37726
37751
|
let base = home;
|
|
37727
37752
|
try {
|
|
37728
|
-
|
|
37753
|
+
fs29.accessSync(systemApps, fs29.constants.W_OK);
|
|
37729
37754
|
base = "";
|
|
37730
37755
|
} catch {
|
|
37731
37756
|
}
|
|
37732
|
-
const appDir = base ?
|
|
37733
|
-
const macOSDir =
|
|
37757
|
+
const appDir = base ? path27.join(home, "Applications", "Command OS.app") : path27.join(systemApps, "Command OS.app");
|
|
37758
|
+
const macOSDir = path27.join(appDir, "Contents", "MacOS");
|
|
37734
37759
|
const script = [
|
|
37735
37760
|
"#!/bin/bash",
|
|
37736
37761
|
"# GHL Command \u2014 Command OS launcher (regenerate: ghl-mcp install-launcher)",
|
|
@@ -37759,13 +37784,13 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
37759
37784
|
targetPath: appDir,
|
|
37760
37785
|
humanLocation: base ? "your personal Applications folder at ~/Applications (Finder \u2192 Go \u2192 Home \u2192 Applications)" : "your Applications folder \u2014 open Finder \u2192 Applications and look for \u201CCommand OS\u201D",
|
|
37761
37786
|
files: [
|
|
37762
|
-
{ path:
|
|
37763
|
-
{ path:
|
|
37787
|
+
{ path: path27.join(macOSDir, "command-os"), contents: script, executable: true },
|
|
37788
|
+
{ path: path27.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
|
|
37764
37789
|
]
|
|
37765
37790
|
};
|
|
37766
37791
|
}
|
|
37767
37792
|
if (platform2 === "win32") {
|
|
37768
|
-
const target2 =
|
|
37793
|
+
const target2 = path27.join(home, "Desktop", "Command OS.cmd");
|
|
37769
37794
|
return {
|
|
37770
37795
|
platform: platform2,
|
|
37771
37796
|
targetPath: target2,
|
|
@@ -37778,7 +37803,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
37778
37803
|
].join("\r\n"), executable: false }]
|
|
37779
37804
|
};
|
|
37780
37805
|
}
|
|
37781
|
-
const target =
|
|
37806
|
+
const target = path27.join(home, ".local", "share", "applications", "command-os.desktop");
|
|
37782
37807
|
return {
|
|
37783
37808
|
platform: platform2,
|
|
37784
37809
|
targetPath: target,
|
|
@@ -37802,7 +37827,7 @@ function planLauncher(platform2, home, port = 7300, entry = installingEntry()) {
|
|
|
37802
37827
|
function launcherInstalled(port = 7300, platform2 = process.platform, home = os4.homedir()) {
|
|
37803
37828
|
try {
|
|
37804
37829
|
const plan = planLauncher(platform2, home, port, installingEntry());
|
|
37805
|
-
return plan.files.every((f) =>
|
|
37830
|
+
return plan.files.every((f) => fs29.existsSync(f.path));
|
|
37806
37831
|
} catch {
|
|
37807
37832
|
return false;
|
|
37808
37833
|
}
|
|
@@ -37813,8 +37838,8 @@ function installLauncher(argv = []) {
|
|
|
37813
37838
|
const plan = planLauncher(process.platform, os4.homedir(), port, installingEntry());
|
|
37814
37839
|
try {
|
|
37815
37840
|
for (const f of plan.files) {
|
|
37816
|
-
|
|
37817
|
-
|
|
37841
|
+
fs29.mkdirSync(path27.dirname(f.path), { recursive: true });
|
|
37842
|
+
fs29.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
|
|
37818
37843
|
}
|
|
37819
37844
|
} catch (e) {
|
|
37820
37845
|
process.stderr.write(`
|
|
@@ -37834,47 +37859,47 @@ function installLauncher(argv = []) {
|
|
|
37834
37859
|
].join("\n"));
|
|
37835
37860
|
return 0;
|
|
37836
37861
|
}
|
|
37837
|
-
var
|
|
37862
|
+
var fs29, os4, path27;
|
|
37838
37863
|
var init_launcher = __esm({
|
|
37839
37864
|
"src/launcher.ts"() {
|
|
37840
37865
|
"use strict";
|
|
37841
|
-
|
|
37866
|
+
fs29 = __toESM(require("fs"));
|
|
37842
37867
|
os4 = __toESM(require("os"));
|
|
37843
|
-
|
|
37868
|
+
path27 = __toESM(require("path"));
|
|
37844
37869
|
}
|
|
37845
37870
|
});
|
|
37846
37871
|
|
|
37847
37872
|
// src/npx-cache-check.ts
|
|
37848
37873
|
function defaultNpxCacheDir(env2 = process.env, platform2 = process.platform, home = os5.homedir()) {
|
|
37849
37874
|
const configured = env2.npm_config_cache?.trim();
|
|
37850
|
-
const cache = configured ? configured : platform2 === "win32" ?
|
|
37851
|
-
return
|
|
37875
|
+
const cache = configured ? configured : platform2 === "win32" ? path28.join(env2.LOCALAPPDATA?.trim() || path28.join(home, "AppData", "Local"), "npm-cache") : path28.join(home, ".npm");
|
|
37876
|
+
return path28.join(cache, "_npx");
|
|
37852
37877
|
}
|
|
37853
37878
|
function damagedNpxDownloads(npxDir) {
|
|
37854
37879
|
let entries;
|
|
37855
37880
|
try {
|
|
37856
|
-
entries =
|
|
37881
|
+
entries = fs30.readdirSync(npxDir, { withFileTypes: true });
|
|
37857
37882
|
} catch {
|
|
37858
37883
|
return [];
|
|
37859
37884
|
}
|
|
37860
37885
|
const damaged = [];
|
|
37861
37886
|
const gone = (p) => {
|
|
37862
37887
|
try {
|
|
37863
|
-
|
|
37888
|
+
fs30.statSync(p);
|
|
37864
37889
|
return false;
|
|
37865
37890
|
} catch (e) {
|
|
37866
37891
|
return e.code === "ENOENT";
|
|
37867
37892
|
}
|
|
37868
37893
|
};
|
|
37869
37894
|
for (const entry of entries) {
|
|
37870
|
-
const dir =
|
|
37871
|
-
const pkg2 =
|
|
37895
|
+
const dir = path28.join(npxDir, entry.name);
|
|
37896
|
+
const pkg2 = path28.join(dir, PACKAGE_SUBDIR);
|
|
37872
37897
|
try {
|
|
37873
|
-
if (!
|
|
37898
|
+
if (!fs30.statSync(dir).isDirectory() || !fs30.statSync(pkg2).isDirectory()) continue;
|
|
37874
37899
|
} catch {
|
|
37875
37900
|
continue;
|
|
37876
37901
|
}
|
|
37877
|
-
if (gone(
|
|
37902
|
+
if (gone(path28.join(dir, "package.json")) || gone(path28.join(pkg2, "package.json"))) damaged.push(dir);
|
|
37878
37903
|
}
|
|
37879
37904
|
return damaged.sort();
|
|
37880
37905
|
}
|
|
@@ -37883,14 +37908,14 @@ function damagedDownloadLine(dirs) {
|
|
|
37883
37908
|
const one = dirs.length === 1;
|
|
37884
37909
|
return `${one ? "One earlier download" : "Earlier downloads"} of GHL Command on this computer ${one ? "is" : "are"} damaged, which makes Claude say "Connection closed" instead of starting it. Delete ${one ? "this folder" : "these folders"}, then start Claude again: ${dirs.join(" and ")}`;
|
|
37885
37910
|
}
|
|
37886
|
-
var
|
|
37911
|
+
var fs30, os5, path28, PACKAGE_SUBDIR;
|
|
37887
37912
|
var init_npx_cache_check = __esm({
|
|
37888
37913
|
"src/npx-cache-check.ts"() {
|
|
37889
37914
|
"use strict";
|
|
37890
|
-
|
|
37915
|
+
fs30 = __toESM(require("node:fs"));
|
|
37891
37916
|
os5 = __toESM(require("node:os"));
|
|
37892
|
-
|
|
37893
|
-
PACKAGE_SUBDIR =
|
|
37917
|
+
path28 = __toESM(require("node:path"));
|
|
37918
|
+
PACKAGE_SUBDIR = path28.join("node_modules", "@elitedcs", "ghl-mcp");
|
|
37894
37919
|
}
|
|
37895
37920
|
});
|
|
37896
37921
|
|
|
@@ -37914,7 +37939,7 @@ function defaultIO() {
|
|
|
37914
37939
|
npxCacheDir: defaultNpxCacheDir(),
|
|
37915
37940
|
out: (l) => process.stdout.write(l + "\n"),
|
|
37916
37941
|
err: (l) => process.stderr.write(l + "\n"),
|
|
37917
|
-
rename: (from, to) =>
|
|
37942
|
+
rename: (from, to) => fs31.renameSync(from, to),
|
|
37918
37943
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
37919
37944
|
};
|
|
37920
37945
|
}
|
|
@@ -37924,32 +37949,32 @@ function candidateConfigPaths(opts) {
|
|
|
37924
37949
|
const file2 = "claude_desktop_config.json";
|
|
37925
37950
|
const candidates = [];
|
|
37926
37951
|
if (platform2 === "darwin") {
|
|
37927
|
-
candidates.push(
|
|
37952
|
+
candidates.push(path29.join(home, "Library", "Application Support", "Claude", file2));
|
|
37928
37953
|
} else if (platform2 === "win32") {
|
|
37929
|
-
const appData = opts?.appData ?? process.env.APPDATA ??
|
|
37930
|
-
candidates.push(
|
|
37931
|
-
const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ??
|
|
37932
|
-
const packagesDir =
|
|
37954
|
+
const appData = opts?.appData ?? process.env.APPDATA ?? path29.join(home, "AppData", "Roaming");
|
|
37955
|
+
candidates.push(path29.join(appData, "Claude", file2));
|
|
37956
|
+
const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path29.join(home, "AppData", "Local");
|
|
37957
|
+
const packagesDir = path29.join(localAppData, "Packages");
|
|
37933
37958
|
try {
|
|
37934
|
-
for (const entry of
|
|
37959
|
+
for (const entry of fs31.readdirSync(packagesDir)) {
|
|
37935
37960
|
if (entry.startsWith("Claude_")) {
|
|
37936
|
-
candidates.push(
|
|
37961
|
+
candidates.push(path29.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file2));
|
|
37937
37962
|
}
|
|
37938
37963
|
}
|
|
37939
37964
|
} catch {
|
|
37940
37965
|
}
|
|
37941
37966
|
} else {
|
|
37942
|
-
candidates.push(
|
|
37967
|
+
candidates.push(path29.join(home, ".config", "Claude", file2));
|
|
37943
37968
|
}
|
|
37944
37969
|
return candidates;
|
|
37945
37970
|
}
|
|
37946
37971
|
function resolveConfigPath(explicitPath) {
|
|
37947
37972
|
if (explicitPath) {
|
|
37948
|
-
const p =
|
|
37949
|
-
return { configPath: p, exists:
|
|
37973
|
+
const p = path29.resolve(explicitPath);
|
|
37974
|
+
return { configPath: p, exists: fs31.existsSync(p) };
|
|
37950
37975
|
}
|
|
37951
37976
|
const candidates = candidateConfigPaths();
|
|
37952
|
-
const existing = candidates.filter((c) =>
|
|
37977
|
+
const existing = candidates.filter((c) => fs31.existsSync(c));
|
|
37953
37978
|
if (existing.length > 1) {
|
|
37954
37979
|
throw new InstallStop(
|
|
37955
37980
|
EXIT_REFUSED,
|
|
@@ -37966,22 +37991,22 @@ function resolveConfigPath(explicitPath) {
|
|
|
37966
37991
|
function resolveSymlinkPolicy(configPath, explicitPath, home = os6.homedir()) {
|
|
37967
37992
|
let st;
|
|
37968
37993
|
try {
|
|
37969
|
-
st =
|
|
37994
|
+
st = fs31.lstatSync(configPath);
|
|
37970
37995
|
} catch {
|
|
37971
37996
|
return configPath;
|
|
37972
37997
|
}
|
|
37973
37998
|
if (!st.isSymbolicLink()) return configPath;
|
|
37974
37999
|
let real;
|
|
37975
38000
|
try {
|
|
37976
|
-
real =
|
|
38001
|
+
real = fs31.realpathSync(configPath);
|
|
37977
38002
|
} catch {
|
|
37978
38003
|
throw new InstallStop(
|
|
37979
38004
|
EXIT_REFUSED,
|
|
37980
38005
|
`${configPath} is a link that points to a file that does not exist. Fix or remove the link, or run again with --path to a real file. Nothing was changed.`
|
|
37981
38006
|
);
|
|
37982
38007
|
}
|
|
37983
|
-
const rel =
|
|
37984
|
-
const outsideHome = rel.startsWith("..") ||
|
|
38008
|
+
const rel = path29.relative(path29.resolve(home), real);
|
|
38009
|
+
const outsideHome = rel.startsWith("..") || path29.isAbsolute(rel);
|
|
37985
38010
|
if (outsideHome && !explicitPath) {
|
|
37986
38011
|
throw new InstallStop(
|
|
37987
38012
|
EXIT_REFUSED,
|
|
@@ -37991,8 +38016,8 @@ function resolveSymlinkPolicy(configPath, explicitPath, home = os6.homedir()) {
|
|
|
37991
38016
|
return real;
|
|
37992
38017
|
}
|
|
37993
38018
|
function readConfigBytes(configPath) {
|
|
37994
|
-
const bytes =
|
|
37995
|
-
const stat =
|
|
38019
|
+
const bytes = fs31.readFileSync(configPath);
|
|
38020
|
+
const stat = fs31.statSync(configPath);
|
|
37996
38021
|
if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 254 && bytes[1] === 255)) {
|
|
37997
38022
|
throw new InstallStop(
|
|
37998
38023
|
EXIT_REFUSED,
|
|
@@ -38066,23 +38091,23 @@ function backupStamp(now) {
|
|
|
38066
38091
|
}
|
|
38067
38092
|
function chooseBackupPath(configPath, now = /* @__PURE__ */ new Date()) {
|
|
38068
38093
|
const base = `${configPath}.bak-${backupStamp(now)}`;
|
|
38069
|
-
if (!
|
|
38094
|
+
if (!fs31.existsSync(base)) return base;
|
|
38070
38095
|
for (let i = 2; ; i++) {
|
|
38071
38096
|
const candidate = `${base}-${i}`;
|
|
38072
|
-
if (!
|
|
38097
|
+
if (!fs31.existsSync(candidate)) return candidate;
|
|
38073
38098
|
}
|
|
38074
38099
|
}
|
|
38075
38100
|
function writeVerifiedBackup(configPath, originalBytes) {
|
|
38076
38101
|
const backupPath = chooseBackupPath(configPath);
|
|
38077
38102
|
try {
|
|
38078
|
-
|
|
38079
|
-
const readBack =
|
|
38103
|
+
fs31.writeFileSync(backupPath, originalBytes);
|
|
38104
|
+
const readBack = fs31.readFileSync(backupPath);
|
|
38080
38105
|
if (!readBack.equals(originalBytes)) {
|
|
38081
38106
|
throw new Error("backup read-back did not match");
|
|
38082
38107
|
}
|
|
38083
38108
|
} catch (e) {
|
|
38084
38109
|
try {
|
|
38085
|
-
|
|
38110
|
+
fs31.rmSync(backupPath, { force: true });
|
|
38086
38111
|
} catch {
|
|
38087
38112
|
}
|
|
38088
38113
|
throw new InstallStop(
|
|
@@ -38093,7 +38118,7 @@ function writeVerifiedBackup(configPath, originalBytes) {
|
|
|
38093
38118
|
return backupPath;
|
|
38094
38119
|
}
|
|
38095
38120
|
function sha256(bytes) {
|
|
38096
|
-
return (0,
|
|
38121
|
+
return (0, import_node_crypto10.createHash)("sha256").update(bytes).digest("hex");
|
|
38097
38122
|
}
|
|
38098
38123
|
function baselineOf(bytes, stat) {
|
|
38099
38124
|
return { size: stat.size, mtimeMs: stat.mtimeMs, hash: sha256(bytes) };
|
|
@@ -38101,9 +38126,9 @@ function baselineOf(bytes, stat) {
|
|
|
38101
38126
|
function assertNotStale(configPath, baseline) {
|
|
38102
38127
|
let ok = false;
|
|
38103
38128
|
try {
|
|
38104
|
-
const st =
|
|
38129
|
+
const st = fs31.statSync(configPath);
|
|
38105
38130
|
if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
|
|
38106
|
-
ok = sha256(
|
|
38131
|
+
ok = sha256(fs31.readFileSync(configPath)) === baseline.hash;
|
|
38107
38132
|
} else {
|
|
38108
38133
|
ok = false;
|
|
38109
38134
|
}
|
|
@@ -38118,14 +38143,14 @@ function assertNotStale(configPath, baseline) {
|
|
|
38118
38143
|
}
|
|
38119
38144
|
}
|
|
38120
38145
|
async function atomicReplace(opts) {
|
|
38121
|
-
const dir =
|
|
38122
|
-
const tempPath =
|
|
38123
|
-
const fd =
|
|
38146
|
+
const dir = path29.dirname(opts.configPath);
|
|
38147
|
+
const tempPath = path29.join(dir, `.${path29.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto10.randomBytes)(4).toString("hex")}`);
|
|
38148
|
+
const fd = fs31.openSync(tempPath, "w");
|
|
38124
38149
|
try {
|
|
38125
|
-
|
|
38126
|
-
|
|
38150
|
+
fs31.writeFileSync(fd, opts.newBytes);
|
|
38151
|
+
fs31.fsyncSync(fd);
|
|
38127
38152
|
} finally {
|
|
38128
|
-
|
|
38153
|
+
fs31.closeSync(fd);
|
|
38129
38154
|
}
|
|
38130
38155
|
try {
|
|
38131
38156
|
for (let attempt = 0; ; attempt++) {
|
|
@@ -38150,17 +38175,17 @@ async function atomicReplace(opts) {
|
|
|
38150
38175
|
}
|
|
38151
38176
|
} catch (e) {
|
|
38152
38177
|
try {
|
|
38153
|
-
|
|
38178
|
+
fs31.rmSync(tempPath, { force: true });
|
|
38154
38179
|
} catch {
|
|
38155
38180
|
}
|
|
38156
38181
|
throw e;
|
|
38157
38182
|
}
|
|
38158
38183
|
try {
|
|
38159
|
-
const dirFd =
|
|
38184
|
+
const dirFd = fs31.openSync(dir, "r");
|
|
38160
38185
|
try {
|
|
38161
|
-
|
|
38186
|
+
fs31.fsyncSync(dirFd);
|
|
38162
38187
|
} finally {
|
|
38163
|
-
|
|
38188
|
+
fs31.closeSync(dirFd);
|
|
38164
38189
|
}
|
|
38165
38190
|
} catch {
|
|
38166
38191
|
}
|
|
@@ -38218,7 +38243,7 @@ async function runInstall(argv, ioOverride) {
|
|
|
38218
38243
|
try {
|
|
38219
38244
|
const resolved2 = resolveConfigPath(flags.path);
|
|
38220
38245
|
const configPath = resolveSymlinkPolicy(resolved2.configPath, flags.path !== void 0);
|
|
38221
|
-
const exists =
|
|
38246
|
+
const exists = fs31.existsSync(configPath);
|
|
38222
38247
|
let read2 = null;
|
|
38223
38248
|
let outcome;
|
|
38224
38249
|
if (exists) {
|
|
@@ -38250,7 +38275,7 @@ async function runInstall(argv, ioOverride) {
|
|
|
38250
38275
|
return EXIT_OK;
|
|
38251
38276
|
}
|
|
38252
38277
|
const backupPath = exists && read2 ? writeVerifiedBackup(configPath, read2.bytes) : null;
|
|
38253
|
-
if (!exists)
|
|
38278
|
+
if (!exists) fs31.mkdirSync(path29.dirname(configPath), { recursive: true });
|
|
38254
38279
|
await atomicReplace({
|
|
38255
38280
|
configPath,
|
|
38256
38281
|
newBytes,
|
|
@@ -38269,14 +38294,14 @@ async function runInstall(argv, ioOverride) {
|
|
|
38269
38294
|
return EXIT_ABORTED;
|
|
38270
38295
|
}
|
|
38271
38296
|
}
|
|
38272
|
-
var
|
|
38297
|
+
var fs31, os6, path29, import_node_crypto10, import_node_util, import_json5, SERVER_KEY, DESIRED_ENTRY, EXIT_OK, EXIT_USAGE, EXIT_REFUSED, EXIT_ABORTED, RENAME_RETRY_DELAYS_MS, LOCK_ERROR_CODES, BOM_UTF8, InstallStop;
|
|
38273
38298
|
var init_config_installer = __esm({
|
|
38274
38299
|
"src/config-installer.ts"() {
|
|
38275
38300
|
"use strict";
|
|
38276
|
-
|
|
38301
|
+
fs31 = __toESM(require("node:fs"));
|
|
38277
38302
|
os6 = __toESM(require("node:os"));
|
|
38278
|
-
|
|
38279
|
-
|
|
38303
|
+
path29 = __toESM(require("node:path"));
|
|
38304
|
+
import_node_crypto10 = require("node:crypto");
|
|
38280
38305
|
import_node_util = require("node:util");
|
|
38281
38306
|
import_json5 = __toESM(require("json5"));
|
|
38282
38307
|
init_npx_cache_check();
|
|
@@ -38309,9 +38334,9 @@ function errLine(msg3) {
|
|
|
38309
38334
|
function preflightWritable() {
|
|
38310
38335
|
try {
|
|
38311
38336
|
const dir = ensureAppDataDir();
|
|
38312
|
-
const probe =
|
|
38313
|
-
|
|
38314
|
-
|
|
38337
|
+
const probe = path30.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
|
|
38338
|
+
fs32.writeFileSync(probe, "ok");
|
|
38339
|
+
fs32.unlinkSync(probe);
|
|
38315
38340
|
return true;
|
|
38316
38341
|
} catch (error) {
|
|
38317
38342
|
errLine(`Config dir is not writable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -38580,13 +38605,13 @@ async function runCli(subcommand, argv) {
|
|
|
38580
38605
|
return EXIT_USAGE2;
|
|
38581
38606
|
}
|
|
38582
38607
|
}
|
|
38583
|
-
var import_node_util2,
|
|
38608
|
+
var import_node_util2, fs32, path30, import_crypto2, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
|
|
38584
38609
|
var init_cli = __esm({
|
|
38585
38610
|
"src/cli.ts"() {
|
|
38586
38611
|
"use strict";
|
|
38587
38612
|
import_node_util2 = require("node:util");
|
|
38588
|
-
|
|
38589
|
-
|
|
38613
|
+
fs32 = __toESM(require("fs"));
|
|
38614
|
+
path30 = __toESM(require("path"));
|
|
38590
38615
|
import_crypto2 = require("crypto");
|
|
38591
38616
|
init_ghl_client();
|
|
38592
38617
|
init_token_registry();
|
|
@@ -39132,11 +39157,11 @@ __export(intake_overlay_exports, {
|
|
|
39132
39157
|
writeOverlay: () => writeOverlay
|
|
39133
39158
|
});
|
|
39134
39159
|
function overlayPath() {
|
|
39135
|
-
return
|
|
39160
|
+
return path31.join(appDataDir(), "intake-overlay.json");
|
|
39136
39161
|
}
|
|
39137
39162
|
function readOverlay() {
|
|
39138
39163
|
try {
|
|
39139
|
-
const raw = JSON.parse(
|
|
39164
|
+
const raw = JSON.parse(fs33.readFileSync(overlayPath(), "utf8"));
|
|
39140
39165
|
if (raw && typeof raw === "object") return raw;
|
|
39141
39166
|
} catch {
|
|
39142
39167
|
}
|
|
@@ -39144,14 +39169,14 @@ function readOverlay() {
|
|
|
39144
39169
|
}
|
|
39145
39170
|
function writeOverlay(layer) {
|
|
39146
39171
|
ensureAppDataDir();
|
|
39147
|
-
|
|
39172
|
+
fs33.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
|
|
39148
39173
|
}
|
|
39149
|
-
var
|
|
39174
|
+
var fs33, path31;
|
|
39150
39175
|
var init_intake_overlay = __esm({
|
|
39151
39176
|
"src/intake-overlay.ts"() {
|
|
39152
39177
|
"use strict";
|
|
39153
|
-
|
|
39154
|
-
|
|
39178
|
+
fs33 = __toESM(require("fs"));
|
|
39179
|
+
path31 = __toESM(require("path"));
|
|
39155
39180
|
init_credentials_store();
|
|
39156
39181
|
}
|
|
39157
39182
|
});
|
|
@@ -39307,9 +39332,9 @@ function renderReport2(opts) {
|
|
|
39307
39332
|
if (opts.answered && opts.answered.length) {
|
|
39308
39333
|
out.push('<h2>What we started from, in your words</h2><table class="qa">');
|
|
39309
39334
|
for (var a = 0; a < opts.answered.length; a++) {
|
|
39310
|
-
var
|
|
39311
|
-
var numeric =
|
|
39312
|
-
out.push("<tr><td>" + esc7(
|
|
39335
|
+
var q3 = opts.answered[a];
|
|
39336
|
+
var numeric = q3.unit !== void 0 ? q3.unit !== "text" : isFigure(q3.value);
|
|
39337
|
+
out.push("<tr><td>" + esc7(q3.ask) + '</td><td class="' + (numeric ? "num" : "said") + '">' + (q3.unknown ? '<span class="unk">you did not know</span>' : esc7(q3.value)) + "</td></tr>");
|
|
39313
39338
|
}
|
|
39314
39339
|
out.push("</table>");
|
|
39315
39340
|
}
|
|
@@ -39454,6 +39479,491 @@ var init_report = __esm({
|
|
|
39454
39479
|
}
|
|
39455
39480
|
});
|
|
39456
39481
|
|
|
39482
|
+
// src/command-os/client-calls.ts
|
|
39483
|
+
var client_calls_exports = {};
|
|
39484
|
+
__export(client_calls_exports, {
|
|
39485
|
+
ANALYSIS_MIN_WORDS: () => ANALYSIS_MIN_WORDS,
|
|
39486
|
+
CLIENT_FILES_ROOT: () => CLIENT_FILES_ROOT,
|
|
39487
|
+
CONFIRM_INSTRUCTION: () => CONFIRM_INSTRUCTION,
|
|
39488
|
+
EXISTING_WITH_CONTACT: () => EXISTING_WITH_CONTACT,
|
|
39489
|
+
RECORDERS: () => RECORDERS,
|
|
39490
|
+
SOLUTION_KINDS: () => SOLUTION_KINDS,
|
|
39491
|
+
UNCLEAN_RECORD: () => UNCLEAN_RECORD,
|
|
39492
|
+
buildClientFile: () => buildClientFile,
|
|
39493
|
+
callDate: () => callDate,
|
|
39494
|
+
confirmList: () => confirmList,
|
|
39495
|
+
gateAnalysis: () => gateAnalysis,
|
|
39496
|
+
processClientCall: () => processClientCall,
|
|
39497
|
+
safeName: () => safeName,
|
|
39498
|
+
writeClientFileLocally: () => writeClientFileLocally
|
|
39499
|
+
});
|
|
39500
|
+
function gateAnalysis(transcript, analysis) {
|
|
39501
|
+
const rejected = [];
|
|
39502
|
+
const kept = {
|
|
39503
|
+
saidTheyWant: (analysis.saidTheyWant ?? []).map(clean).filter(Boolean),
|
|
39504
|
+
actuallyNeed: [],
|
|
39505
|
+
unnamedProblems: [],
|
|
39506
|
+
recommendedSolutions: [],
|
|
39507
|
+
nextCallQuestions: (analysis.nextCallQuestions ?? []).map(clean).filter(Boolean)
|
|
39508
|
+
};
|
|
39509
|
+
const check = (section2, item, quote) => {
|
|
39510
|
+
if (!item) {
|
|
39511
|
+
rejected.push({ section: section2, item, quote, why: "the point itself was empty." });
|
|
39512
|
+
return false;
|
|
39513
|
+
}
|
|
39514
|
+
const said = quoteCheck(transcript, quote, { minWords: ANALYSIS_MIN_WORDS });
|
|
39515
|
+
if (said.verdict === "refused") {
|
|
39516
|
+
rejected.push({ section: section2, item, quote, why: said.because });
|
|
39517
|
+
return false;
|
|
39518
|
+
}
|
|
39519
|
+
return true;
|
|
39520
|
+
};
|
|
39521
|
+
for (const section2 of ["actuallyNeed", "unnamedProblems"]) {
|
|
39522
|
+
for (const p of analysis[section2] ?? []) {
|
|
39523
|
+
const point = clean(p?.point), quote = String(p?.quote ?? "").trim();
|
|
39524
|
+
if (check(section2, point, quote)) kept[section2].push({ point, quote });
|
|
39525
|
+
}
|
|
39526
|
+
}
|
|
39527
|
+
for (const s of analysis.recommendedSolutions ?? []) {
|
|
39528
|
+
const solution = clean(s?.solution), quote = String(s?.quote ?? "").trim();
|
|
39529
|
+
if (!SOLUTION_KINDS.includes(s?.kind)) {
|
|
39530
|
+
rejected.push({ section: "recommendedSolutions", item: solution, quote, why: `"${String(s?.kind)}" is not a kind; use ${SOLUTION_KINDS.join(", ")}.` });
|
|
39531
|
+
continue;
|
|
39532
|
+
}
|
|
39533
|
+
if (check("recommendedSolutions", solution, quote)) kept.recommendedSolutions.push({ solution, kind: s.kind, why: clean(s?.why), quote });
|
|
39534
|
+
}
|
|
39535
|
+
return { kept, rejected };
|
|
39536
|
+
}
|
|
39537
|
+
function safeName(raw, max = 80) {
|
|
39538
|
+
let s = String(raw ?? "").normalize("NFC").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/[/\\:*?"<>|]/g, " ").replace(/(^|\s)\.+(?=\s|$)/g, " ").replace(/\s+/g, " ").trim().replace(/^[.\s]+|[.\s]+$/g, "");
|
|
39539
|
+
const points = Array.from(s);
|
|
39540
|
+
if (points.length > max) s = points.slice(0, max).join("").replace(/[.\s]+$/g, "");
|
|
39541
|
+
if (WINDOWS_RESERVED.test(s)) return void 0;
|
|
39542
|
+
return s || void 0;
|
|
39543
|
+
}
|
|
39544
|
+
function callDate(source, now) {
|
|
39545
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(source?.recordedAt ?? "").trim());
|
|
39546
|
+
if (m) {
|
|
39547
|
+
const d = /* @__PURE__ */ new Date(`${m[1]}-${m[2]}-${m[3]}T00:00:00Z`);
|
|
39548
|
+
if (!isNaN(d.getTime()) && d.toISOString().slice(0, 10) === `${m[1]}-${m[2]}-${m[3]}`) return { date: d.toISOString().slice(0, 10), fromRecording: true };
|
|
39549
|
+
}
|
|
39550
|
+
return { date: now.toISOString().slice(0, 10), fromRecording: false };
|
|
39551
|
+
}
|
|
39552
|
+
function confirmList(held) {
|
|
39553
|
+
return held.map((h) => {
|
|
39554
|
+
const saved = /already has "([^"]*)" saved/.exec(h.because)?.[1];
|
|
39555
|
+
return `${ASK.get(h.questionId) ?? h.questionId} \u2192 ${h.value} (their words: ${q2(h.quote)})${saved !== void 0 ? ` [replaces the saved "${saved}"]` : ""}`;
|
|
39556
|
+
});
|
|
39557
|
+
}
|
|
39558
|
+
function analysisMarkdown(a, level) {
|
|
39559
|
+
return [
|
|
39560
|
+
`${level} What they said they want`,
|
|
39561
|
+
bullet(a.saidTheyWant),
|
|
39562
|
+
"",
|
|
39563
|
+
`${level} What they actually need`,
|
|
39564
|
+
bullet(a.actuallyNeed.map((p) => `${stop(p.point)} They said: ${q2(p.quote)}`)),
|
|
39565
|
+
"",
|
|
39566
|
+
`${level} Problems they did not name`,
|
|
39567
|
+
bullet(a.unnamedProblems.map((p) => `${stop(p.point)} They said: ${q2(p.quote)}`)),
|
|
39568
|
+
"",
|
|
39569
|
+
`${level} Recommended solutions`,
|
|
39570
|
+
bullet(a.recommendedSolutions.map((s) => `**${s.solution}** (${s.kind}).${s.why ? ` ${stop(s.why)}` : ""} They said: ${q2(s.quote)}`)),
|
|
39571
|
+
"",
|
|
39572
|
+
`${level} Questions for the next call`,
|
|
39573
|
+
bullet(a.nextCallQuestions)
|
|
39574
|
+
].join("\n");
|
|
39575
|
+
}
|
|
39576
|
+
function buildClientFile(args) {
|
|
39577
|
+
const client = safeName(args.clientName) ?? "Unnamed client";
|
|
39578
|
+
const folderParts = [...CLIENT_FILES_ROOT, client];
|
|
39579
|
+
const recorder = args.source?.recorder ?? "Call";
|
|
39580
|
+
const title2 = args.source?.title?.trim() || "call";
|
|
39581
|
+
const stem = safeName(`${args.date} ${recorder} ${title2}`, 120) ?? `${args.date} call`;
|
|
39582
|
+
const name = `${stem}.md`;
|
|
39583
|
+
const transcriptName = `${stem} transcript.md`;
|
|
39584
|
+
const ask = (id) => ASK.get(id) ?? id;
|
|
39585
|
+
const src = args.source ? describeSource({ ...args.source, recordedAt: args.date }) : "Not recorded (no source was given)";
|
|
39586
|
+
const call2 = [
|
|
39587
|
+
`# Call with ${client}, ${args.date}`,
|
|
39588
|
+
"",
|
|
39589
|
+
`Source: ${src}.`,
|
|
39590
|
+
`Assessment: ${args.saveLine}`,
|
|
39591
|
+
"",
|
|
39592
|
+
"## Answers saved or ready to save",
|
|
39593
|
+
bullet(args.gated.accepted.map((a) => `${ask(a.questionId)} **${a.value}**${args.confirmedByOwner?.includes(a.questionId) ? " (confirmed by you)" : ""}. They said: ${q2(a.quote)}`)),
|
|
39594
|
+
"",
|
|
39595
|
+
"## Waiting for your confirmation",
|
|
39596
|
+
...args.gated.held.length ? [confirmList(args.gated.held).map((l) => `- ${l}`).join("\n"), "", CONFIRM_INSTRUCTION] : ["- (none)"],
|
|
39597
|
+
"",
|
|
39598
|
+
"## Thrown out",
|
|
39599
|
+
bullet(args.gated.rejected.map((r) => `${ask(r.questionId)} ${r.value}: ${r.because}`)),
|
|
39600
|
+
"",
|
|
39601
|
+
...args.analysis ? [
|
|
39602
|
+
"## Needs analysis (for you, not for the client)",
|
|
39603
|
+
"",
|
|
39604
|
+
analysisMarkdown(args.analysis.kept, "###"),
|
|
39605
|
+
"",
|
|
39606
|
+
...args.analysis.rejected.length ? [
|
|
39607
|
+
"### Left out: the quote was not in the transcript",
|
|
39608
|
+
bullet(args.analysis.rejected.map((r) => `${r.item || "(empty)"}: ${r.why}`)),
|
|
39609
|
+
""
|
|
39610
|
+
] : []
|
|
39611
|
+
] : [],
|
|
39612
|
+
"## Transcript",
|
|
39613
|
+
"",
|
|
39614
|
+
`Kept in its own file: Calls/${transcriptName}. If that file was not written, the recording in ${args.source?.recorder ?? "the recorder"} is the copy.`,
|
|
39615
|
+
""
|
|
39616
|
+
].join("\n");
|
|
39617
|
+
const transcript = [
|
|
39618
|
+
`# Transcript: call with ${client}, ${args.date}`,
|
|
39619
|
+
"",
|
|
39620
|
+
`Source: ${src}.`,
|
|
39621
|
+
"",
|
|
39622
|
+
args.transcript.trim(),
|
|
39623
|
+
""
|
|
39624
|
+
].join("\n");
|
|
39625
|
+
const understanding = [
|
|
39626
|
+
"",
|
|
39627
|
+
`## ${args.date}: ${recorder} call${args.source?.title?.trim() ? `, ${q2(args.source.title)}` : ""}`,
|
|
39628
|
+
"",
|
|
39629
|
+
...args.analysis ? [analysisMarkdown(args.analysis.kept, "###"), ""] : ["No needs analysis was written for this call.", ""],
|
|
39630
|
+
`Assessment: ${args.saveLine}`,
|
|
39631
|
+
`Full call: Calls/${name}`,
|
|
39632
|
+
""
|
|
39633
|
+
].join("\n");
|
|
39634
|
+
return {
|
|
39635
|
+
clientName: client,
|
|
39636
|
+
folder: folderParts.join("/"),
|
|
39637
|
+
folderParts,
|
|
39638
|
+
call: { folderParts: [...folderParts, "Calls"], name, mode: "create-new", content: call2 },
|
|
39639
|
+
transcript: { folderParts: [...folderParts, "Calls"], name: transcriptName, mode: "create-new", optional: true, characters: args.transcript.trim().length, content: transcript },
|
|
39640
|
+
understanding: {
|
|
39641
|
+
folderParts,
|
|
39642
|
+
name: "Client understanding.md",
|
|
39643
|
+
mode: "append",
|
|
39644
|
+
headerIfNew: `# Client understanding: ${client}
|
|
39645
|
+
|
|
39646
|
+
What we know about this client, newest call last. Advice for the agency, never sent to the client.
|
|
39647
|
+
`,
|
|
39648
|
+
append: understanding
|
|
39649
|
+
},
|
|
39650
|
+
howToWrite: `Search for each folder in folderParts before creating it, one level at a time, and use the one that exists; create it only when the search finds none, and never make a second copy of the tree. Write call.content as a NEW file named call.name in the Calls folder; if that name is taken, add " (2)" rather than replacing it. Write transcript.content as its own new file, transcript.name, beside it; it is optional: when it is too large to write in one go (see transcript.characters), skip it, and a link to the recording or the recorder's own copy is enough. For understanding: read "Client understanding.md", add understanding.append at the end, write it back, then read it again and check every earlier dated section is still there. If it does not exist yet, create it with headerIfNew followed by the append. Never drop what was there: it is the running record of every call.`
|
|
39651
|
+
};
|
|
39652
|
+
}
|
|
39653
|
+
function inside(base, p) {
|
|
39654
|
+
return p.startsWith(base.endsWith(path32.sep) ? base : base + path32.sep);
|
|
39655
|
+
}
|
|
39656
|
+
function writeClientFileLocally(baseDir, file2) {
|
|
39657
|
+
try {
|
|
39658
|
+
if (!path32.isAbsolute(baseDir)) return { written: false, error: `the client files folder must be a full path, not "${baseDir}".` };
|
|
39659
|
+
const base = path32.resolve(baseDir);
|
|
39660
|
+
let st;
|
|
39661
|
+
try {
|
|
39662
|
+
st = fs34.statSync(base);
|
|
39663
|
+
} catch {
|
|
39664
|
+
return { written: false, error: `the client files folder ${base} does not exist. If it is your Google Drive folder, Google Drive for desktop needs to be installed and signed in.` };
|
|
39665
|
+
}
|
|
39666
|
+
if (!st.isDirectory()) return { written: false, error: `${base} is not a folder.` };
|
|
39667
|
+
const realBase = fs34.realpathSync(base);
|
|
39668
|
+
const callsDir = path32.resolve(base, ...file2.call.folderParts);
|
|
39669
|
+
const clientDir = path32.resolve(base, ...file2.understanding.folderParts);
|
|
39670
|
+
if (!inside(base, callsDir) || !inside(base, clientDir)) return { written: false, error: "the client folder name would land outside the client files folder." };
|
|
39671
|
+
let cur = base;
|
|
39672
|
+
for (const part of file2.call.folderParts) {
|
|
39673
|
+
cur = path32.join(cur, part);
|
|
39674
|
+
let here;
|
|
39675
|
+
try {
|
|
39676
|
+
here = fs34.lstatSync(cur);
|
|
39677
|
+
} catch {
|
|
39678
|
+
}
|
|
39679
|
+
if (here && (here.isSymbolicLink() || !here.isDirectory())) {
|
|
39680
|
+
return { written: false, error: `${cur} is a link or a file, not a folder, so nothing was written.` };
|
|
39681
|
+
}
|
|
39682
|
+
if (!here) fs34.mkdirSync(cur, { mode: 448 });
|
|
39683
|
+
}
|
|
39684
|
+
if (!inside(realBase, fs34.realpathSync(callsDir))) return { written: false, error: "the client folder resolves outside the client files folder, so nothing was written." };
|
|
39685
|
+
const stem = file2.call.name.replace(/\.md$/, "");
|
|
39686
|
+
const tStem = file2.transcript.name.replace(/\.md$/, "");
|
|
39687
|
+
let callFile = "", transcriptFile = "";
|
|
39688
|
+
for (let n = 1; n <= 50 && !callFile; n++) {
|
|
39689
|
+
const suffix = n === 1 ? "" : ` (${n})`;
|
|
39690
|
+
const callPath = path32.join(callsDir, `${stem}${suffix}.md`);
|
|
39691
|
+
const tName = `${tStem.slice(0, tStem.length - " transcript".length)}${suffix} transcript.md`;
|
|
39692
|
+
const tPath = path32.join(callsDir, tName);
|
|
39693
|
+
if (fs34.existsSync(callPath) || fs34.existsSync(tPath)) continue;
|
|
39694
|
+
try {
|
|
39695
|
+
fs34.writeFileSync(tPath, file2.transcript.content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
39696
|
+
fs34.writeFileSync(callPath, file2.call.content.split(`Calls/${file2.transcript.name}`).join(`Calls/${tName}`), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
39697
|
+
callFile = callPath;
|
|
39698
|
+
transcriptFile = tPath;
|
|
39699
|
+
} catch (e) {
|
|
39700
|
+
if (e.code !== "EEXIST") throw e;
|
|
39701
|
+
}
|
|
39702
|
+
}
|
|
39703
|
+
if (!callFile) return { written: false, error: `fifty calls named "${stem}" already exist; nothing was overwritten.` };
|
|
39704
|
+
fs34.chmodSync(callFile, 384);
|
|
39705
|
+
fs34.chmodSync(transcriptFile, 384);
|
|
39706
|
+
const understandingFile = path32.join(clientDir, file2.understanding.name);
|
|
39707
|
+
let created = false;
|
|
39708
|
+
try {
|
|
39709
|
+
fs34.writeFileSync(understandingFile, file2.understanding.headerIfNew + file2.understanding.append, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
39710
|
+
created = true;
|
|
39711
|
+
} catch (e) {
|
|
39712
|
+
if (e.code !== "EEXIST") throw e;
|
|
39713
|
+
}
|
|
39714
|
+
if (!created) {
|
|
39715
|
+
const st2 = fs34.lstatSync(understandingFile);
|
|
39716
|
+
if (st2.isSymbolicLink() || !st2.isFile() || st2.nlink > 1) return { written: false, error: `${understandingFile} is a link, not a plain file, so it was not appended to. The call file was written: ${callFile}` };
|
|
39717
|
+
fs34.appendFileSync(understandingFile, file2.understanding.append, { encoding: "utf8", mode: 384 });
|
|
39718
|
+
}
|
|
39719
|
+
fs34.chmodSync(understandingFile, 384);
|
|
39720
|
+
return { written: true, callFile, transcriptFile, understandingFile };
|
|
39721
|
+
} catch (e) {
|
|
39722
|
+
return { written: false, error: `could not write the client file: ${e?.message ?? String(e)}` };
|
|
39723
|
+
}
|
|
39724
|
+
}
|
|
39725
|
+
function contactNames(raw) {
|
|
39726
|
+
const c = raw?.contact ?? {};
|
|
39727
|
+
const s = (k) => typeof c[k] === "string" && c[k].trim() ? c[k].trim() : "";
|
|
39728
|
+
const name = s("name") || [s("firstName"), s("lastName")].filter(Boolean).join(" ") || s("contactName");
|
|
39729
|
+
return { name: name || null, business: s("companyName") || null };
|
|
39730
|
+
}
|
|
39731
|
+
async function withContactLock(key, fn) {
|
|
39732
|
+
const before = contactLocks.get(key) ?? Promise.resolve();
|
|
39733
|
+
let release;
|
|
39734
|
+
const mine = before.then(() => new Promise((r) => {
|
|
39735
|
+
release = r;
|
|
39736
|
+
}));
|
|
39737
|
+
contactLocks.set(key, mine);
|
|
39738
|
+
await before;
|
|
39739
|
+
try {
|
|
39740
|
+
return await fn();
|
|
39741
|
+
} finally {
|
|
39742
|
+
release();
|
|
39743
|
+
if (contactLocks.get(key) === mine) contactLocks.delete(key);
|
|
39744
|
+
}
|
|
39745
|
+
}
|
|
39746
|
+
async function readStrict(client, contactId) {
|
|
39747
|
+
const raw = await client.get(`/contacts/${encodeURIComponent(contactId)}/notes`);
|
|
39748
|
+
return pickLatestStrict(raw?.notes ?? []);
|
|
39749
|
+
}
|
|
39750
|
+
async function saveToContact(input, deps, gated, source) {
|
|
39751
|
+
const contactId = input.contactId;
|
|
39752
|
+
let effective = gated;
|
|
39753
|
+
const out = {
|
|
39754
|
+
status: "NOT SAVED",
|
|
39755
|
+
contactId,
|
|
39756
|
+
contactName: null,
|
|
39757
|
+
businessName: null,
|
|
39758
|
+
savedAnswers: 0,
|
|
39759
|
+
confirmedByOwner: [],
|
|
39760
|
+
confirmationsNotUsed: [],
|
|
39761
|
+
confirmedOnContact: [],
|
|
39762
|
+
reason: null,
|
|
39763
|
+
answersOnContact: null,
|
|
39764
|
+
notesWritten: 0,
|
|
39765
|
+
opportunityNote: null
|
|
39766
|
+
};
|
|
39767
|
+
const done = (o) => ({ outcome: { ...out, ...o }, effective });
|
|
39768
|
+
const fail = (reason3) => done({ status: "NOT SAVED", savedAnswers: 0, reason: reason3 });
|
|
39769
|
+
const msg3 = (e) => e?.message ?? String(e);
|
|
39770
|
+
if (input.existing && Object.keys(input.existing).length) return fail(EXISTING_WITH_CONTACT);
|
|
39771
|
+
try {
|
|
39772
|
+
const who = contactNames(await deps.client.get(`/contacts/${encodeURIComponent(contactId)}`));
|
|
39773
|
+
out.contactName = who.name;
|
|
39774
|
+
out.businessName = who.business;
|
|
39775
|
+
} catch (e) {
|
|
39776
|
+
return fail(`could not open contact ${contactId} in this account (${msg3(e)}).`);
|
|
39777
|
+
}
|
|
39778
|
+
let record;
|
|
39779
|
+
try {
|
|
39780
|
+
const got = await readStrict(deps.client, contactId);
|
|
39781
|
+
if (!got.clean) return fail(UNCLEAN_RECORD);
|
|
39782
|
+
record = got.record;
|
|
39783
|
+
} catch (e) {
|
|
39784
|
+
return fail(`could not read the answers already saved on this contact, and saving without them would hide them (${msg3(e)}).`);
|
|
39785
|
+
}
|
|
39786
|
+
if (record) out.businessName = out.businessName ?? record.state.biz;
|
|
39787
|
+
const sameValue = (a, b) => {
|
|
39788
|
+
const na = numericValue(a), nb = numericValue(b);
|
|
39789
|
+
return a.trim() === b.trim() || Boolean(na && nb && na.n === nb.n && na.percent === nb.percent);
|
|
39790
|
+
};
|
|
39791
|
+
const confirmedIds = /* @__PURE__ */ new Set();
|
|
39792
|
+
const heldAfterConfirm = gated.held.filter((h) => {
|
|
39793
|
+
const c = (input.confirmed ?? []).find((x) => String(x?.questionId) === h.questionId);
|
|
39794
|
+
if (c && sameValue(String(c.value), h.value)) {
|
|
39795
|
+
confirmedIds.add(h.questionId);
|
|
39796
|
+
return false;
|
|
39797
|
+
}
|
|
39798
|
+
return true;
|
|
39799
|
+
});
|
|
39800
|
+
for (const c of input.confirmed ?? []) {
|
|
39801
|
+
const id = String(c?.questionId), value = String(c?.value ?? "");
|
|
39802
|
+
if (confirmedIds.has(id)) continue;
|
|
39803
|
+
const held = gated.held.find((h) => h.questionId === id);
|
|
39804
|
+
out.confirmationsNotUsed.push({ questionId: id, value, why: held ? `the held answer is "${held.value}", not "${value}". Propose "${value}" with its quote, then confirm it.` : gated.rejected.some((r) => r.questionId === id) ? "that answer was thrown out: its quote is not in the transcript." : "no answer to this question is waiting for confirmation." });
|
|
39805
|
+
}
|
|
39806
|
+
const candidates = [...gated.accepted, ...gated.held.filter((h) => confirmedIds.has(h.questionId)).map(({ because: _b, ...a }) => a)];
|
|
39807
|
+
const prior = record?.state.ans ?? {};
|
|
39808
|
+
const replace = new Set(input.proposed.filter((p) => p?.replace === true).map((p) => String(p.questionId)));
|
|
39809
|
+
const conflicts = [];
|
|
39810
|
+
const accepted = candidates.filter((a) => {
|
|
39811
|
+
const had = prior[a.questionId];
|
|
39812
|
+
if (had === void 0 || String(had).trim() === "" || String(had) === a.value || replace.has(a.questionId)) return true;
|
|
39813
|
+
conflicts.push({ ...a, because: `the contact already has "${had}" saved for this question and this call says "${a.value}". Confirm before it replaces the saved answer (send it again with replace: true).` });
|
|
39814
|
+
return false;
|
|
39815
|
+
});
|
|
39816
|
+
out.confirmedByOwner = accepted.filter((a) => confirmedIds.has(a.questionId)).map((a) => a.questionId);
|
|
39817
|
+
effective = { ...gated, accepted, held: [...heldAfterConfirm, ...conflicts], patch: Object.fromEntries(accepted.map((a) => [a.questionId, a.value])) };
|
|
39818
|
+
if (accepted.length === 0) {
|
|
39819
|
+
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 });
|
|
39820
|
+
}
|
|
39821
|
+
const biz = record?.state.biz || clean(input.businessName) || out.businessName || out.contactName;
|
|
39822
|
+
if (!biz) return fail("neither the contact nor a saved assessment has a business name. Give businessName and try again.");
|
|
39823
|
+
out.businessName = out.businessName ?? biz;
|
|
39824
|
+
const skip = { ...record?.state.skip ?? {} };
|
|
39825
|
+
for (const a of accepted) delete skip[a.questionId];
|
|
39826
|
+
const expected = { ...prior, ...effective.patch };
|
|
39827
|
+
const confirmedAll = [.../* @__PURE__ */ new Set([
|
|
39828
|
+
...(record?.confirmedByOwner ?? []).filter((id) => !(id in effective.patch) || out.confirmedByOwner.includes(id)),
|
|
39829
|
+
...out.confirmedByOwner
|
|
39830
|
+
])];
|
|
39831
|
+
const state = { ...record?.state ?? { asm: {} }, biz, ans: expected, skip, asm: record?.state.asm ?? {} };
|
|
39832
|
+
let failure2 = null;
|
|
39833
|
+
try {
|
|
39834
|
+
const saved = await saveAssessment(deps.client, deps.locationId, {
|
|
39835
|
+
assessment: state,
|
|
39836
|
+
contactId,
|
|
39837
|
+
source,
|
|
39838
|
+
createPipeline: input.createPipeline,
|
|
39839
|
+
confirmedByOwner: confirmedAll
|
|
39840
|
+
});
|
|
39841
|
+
if (!saved.saved) failure2 = saved.error ?? "GoHighLevel did not accept the save.";
|
|
39842
|
+
out.notesWritten = saved.notesWritten ?? 0;
|
|
39843
|
+
out.opportunityNote = saved.opportunityNote ?? null;
|
|
39844
|
+
} catch (e) {
|
|
39845
|
+
failure2 = `GoHighLevel reported an error (${msg3(e)}).`;
|
|
39846
|
+
}
|
|
39847
|
+
let back;
|
|
39848
|
+
try {
|
|
39849
|
+
const got = await readStrict(deps.client, contactId);
|
|
39850
|
+
back = got.clean ? got.record : null;
|
|
39851
|
+
} catch (e) {
|
|
39852
|
+
return done({ status: "NOT CONFIRMED", reason: `${failure2 ? failure2 + " " : ""}The contact could not be read back (${msg3(e)}). Treat the answers as not saved until you open the contact.` });
|
|
39853
|
+
}
|
|
39854
|
+
const off = back ? same(back.state.ans, expected) : null;
|
|
39855
|
+
const marks = back?.confirmedByOwner ?? [];
|
|
39856
|
+
if (off && off.length === 0 && marks.length === confirmedAll.length && confirmedAll.every((id) => marks.includes(id))) {
|
|
39857
|
+
return done({ status: "SAVED", savedAnswers: accepted.length, answersOnContact: back.state.ans, confirmedOnContact: marks, reason: failure2 ? `${failure2} The answers did land and read back correctly.` : null });
|
|
39858
|
+
}
|
|
39859
|
+
const unchanged = back ? same(back.state.ans, prior).length === 0 : !record;
|
|
39860
|
+
if (failure2 && unchanged) return done({ status: "NOT SAVED", savedAnswers: 0, reason: failure2, answersOnContact: back?.state.ans ?? null });
|
|
39861
|
+
return done({
|
|
39862
|
+
status: "NOT CONFIRMED",
|
|
39863
|
+
answersOnContact: back?.state.ans ?? null,
|
|
39864
|
+
reason: `${failure2 ? failure2 + " " : ""}Reading the contact back ${back ? `did not match what was saved (${off.length ? off.join(", ") : "the confirmed-by-you marks"}); the contact may have changed at the same time` : "found no complete assessment"}. Treat the answers as not saved until you open the contact.`
|
|
39865
|
+
});
|
|
39866
|
+
}
|
|
39867
|
+
async function processClientCall(input, deps) {
|
|
39868
|
+
let gated = gateExtraction(input.transcript, input.proposed, QUESTION_UNITS);
|
|
39869
|
+
const legacyOf = (gated2) => ({
|
|
39870
|
+
applied: gated2.accepted.length,
|
|
39871
|
+
answers: mergeAccepted(input.existing ?? {}, gated2),
|
|
39872
|
+
confirmBeforeTheseCount: gated2.held.map((h) => ({
|
|
39873
|
+
question: ASK.get(h.questionId) ?? h.questionId,
|
|
39874
|
+
proposedValue: h.value,
|
|
39875
|
+
theyActuallySaid: h.quote,
|
|
39876
|
+
howItWasWorkedOut: h.reasoning ?? null,
|
|
39877
|
+
why: h.because
|
|
39878
|
+
})),
|
|
39879
|
+
thrownOut: gated2.rejected.map((r) => ({ questionId: r.questionId, value: r.value, why: r.because })),
|
|
39880
|
+
note: gated2.rejected.some((r) => /does not appear in the transcript/.test(r.because)) ? "One or more answers cited words that are not in the transcript. Those were discarded, not stored. That check is the only thing standing between a recording and an invented figure in a client's report." : null
|
|
39881
|
+
});
|
|
39882
|
+
const legacy = legacyOf(gated);
|
|
39883
|
+
if (!input.contactId && !input.analysis && !input.source) return legacy;
|
|
39884
|
+
const now = deps.now();
|
|
39885
|
+
const { date, fromRecording } = callDate(input.source, now);
|
|
39886
|
+
const noteSource = input.source ? { recorder: input.source.recorder, recordedAt: date, title: input.source.title?.trim() || void 0, fetchedBy: input.source.fetchedBy } : void 0;
|
|
39887
|
+
const saving = input.contactId ? await withContactLock(`${deps.locationId}:${input.contactId}`, () => saveToContact(input, deps, gated, noteSource)) : null;
|
|
39888
|
+
const save = saving?.outcome ?? null;
|
|
39889
|
+
if (saving) gated = saving.effective;
|
|
39890
|
+
const analysis = input.analysis ? gateAnalysis(input.transcript, input.analysis) : null;
|
|
39891
|
+
const counts = `${plural(gated.held.length, "answer needs", "answers need")} your confirmation, ${plural(gated.rejected.length, "was", "were")} thrown out`;
|
|
39892
|
+
const who = save ? `${save.businessName ?? save.contactName ?? "the contact"}${save.contactName && save.businessName ? ` (${save.contactName})` : ""}, contact ${save.contactId}` : "";
|
|
39893
|
+
let reply;
|
|
39894
|
+
let saveLine;
|
|
39895
|
+
if (!save) {
|
|
39896
|
+
reply = `NOT SAVED: no contact was named, so these answers are only proposed. ${gated.accepted.length} passed the check, ${counts}. Call again with the client's contactId to save them.`;
|
|
39897
|
+
saveLine = "not saved to GoHighLevel (no contact was named).";
|
|
39898
|
+
} else if (save.status === "SAVED") {
|
|
39899
|
+
const byYou = save.confirmedByOwner.length ? ` (${save.confirmedByOwner.length} confirmed by you)` : "";
|
|
39900
|
+
reply = `SAVED ${plural(save.savedAnswers, "answer", "answers")}${byYou} to ${who}. ${counts[0].toUpperCase()}${counts.slice(1)}.${save.reason ? ` Note: ${save.reason}` : ""}`;
|
|
39901
|
+
saveLine = `saved ${plural(save.savedAnswers, "answer", "answers")}${byYou} on the GoHighLevel contact ${save.contactId}.`;
|
|
39902
|
+
} else if (save.status === "NOTHING TO SAVE") {
|
|
39903
|
+
const why = gated.held.length ? "every answer is waiting for your OK (numbers always do, and any that differ from a saved answer)" : "no answer passed the quote check";
|
|
39904
|
+
reply = `NOTHING TO SAVE for ${who}: ${why}. ${counts[0].toUpperCase()}${counts.slice(1)}.`;
|
|
39905
|
+
saveLine = `nothing new saved (${why}).`;
|
|
39906
|
+
} else {
|
|
39907
|
+
reply = `${save.status} for ${who}: ${save.reason} ${save.status === "NOT SAVED" ? "Nothing from this call is on the contact." : ""} Of the proposed answers, ${counts}.`.replace(/\s+/g, " ");
|
|
39908
|
+
saveLine = `${save.status}. ${save.reason}`;
|
|
39909
|
+
}
|
|
39910
|
+
if (save?.opportunityNote) reply += ` ${save.opportunityNote}`;
|
|
39911
|
+
if (save?.confirmationsNotUsed.length) reply += ` ${plural(save.confirmationsNotUsed.length, "confirmation was", "confirmations were")} not used: ${save.confirmationsNotUsed.map((c) => `${c.questionId} (${c.why})`).join("; ")}`;
|
|
39912
|
+
if (analysis?.rejected.length) reply += ` ${plural(analysis.rejected.length, "analysis point was", "analysis points were")} left out because the quote is not in the transcript.`;
|
|
39913
|
+
if (gated.held.length) reply += `
|
|
39914
|
+
|
|
39915
|
+
To confirm:
|
|
39916
|
+
${confirmList(gated.held).map((l) => `- ${l}`).join("\n")}
|
|
39917
|
+
${CONFIRM_INSTRUCTION}`;
|
|
39918
|
+
const clientName = save?.businessName ?? save?.contactName ?? clean(input.businessName);
|
|
39919
|
+
const clientFile = clientName ? buildClientFile({ clientName, date, source: input.source, gated, analysis, transcript: input.transcript, saveLine, confirmedByOwner: save?.confirmedByOwner }) : null;
|
|
39920
|
+
const localCopy = clientFile && deps.clientFilesDir ? writeClientFileLocally(deps.clientFilesDir, clientFile) : null;
|
|
39921
|
+
const { applied, ...legacyRest } = legacyOf(gated);
|
|
39922
|
+
return {
|
|
39923
|
+
// Never hand back unchecked values beside a failed save (review D-3).
|
|
39924
|
+
...save ? (({ answers: _unchecked, ...rest }) => rest)(legacyRest) : { applied, ...legacyRest },
|
|
39925
|
+
...save?.answersOnContact ? { answers: save.answersOnContact } : {},
|
|
39926
|
+
reply,
|
|
39927
|
+
toConfirm: gated.held.map((h) => ({ questionId: h.questionId, question: ASK.get(h.questionId) ?? h.questionId, value: h.value, theirWords: h.quote, replacesSaved: /already has "/.test(h.because) })),
|
|
39928
|
+
saved: save,
|
|
39929
|
+
source: noteSource ? { ...noteSource, dateFromRecording: fromRecording } : null,
|
|
39930
|
+
analysis: analysis ? { ...analysis.kept, leftOutForQuote: analysis.rejected, forTheOwnerOnly: true } : null,
|
|
39931
|
+
clientFile,
|
|
39932
|
+
clientFileNote: clientFile ? null : "No client name was known (no contact, no businessName), so no client file entry was made.",
|
|
39933
|
+
localCopy
|
|
39934
|
+
};
|
|
39935
|
+
}
|
|
39936
|
+
var fs34, path32, RECORDERS, SOLUTION_KINDS, clean, ANALYSIS_MIN_WORDS, CLIENT_FILES_ROOT, WINDOWS_RESERVED, bullet, q2, CONFIRM_INSTRUCTION, stop, EXISTING_WITH_CONTACT, UNCLEAN_RECORD, contactLocks, same, plural;
|
|
39937
|
+
var init_client_calls = __esm({
|
|
39938
|
+
"src/command-os/client-calls.ts"() {
|
|
39939
|
+
"use strict";
|
|
39940
|
+
fs34 = __toESM(require("node:fs"));
|
|
39941
|
+
path32 = __toESM(require("node:path"));
|
|
39942
|
+
init_transcript();
|
|
39943
|
+
init_words();
|
|
39944
|
+
init_store3();
|
|
39945
|
+
init_assessment();
|
|
39946
|
+
RECORDERS = ["Plaud", "Zoom", "Pocket", "Fathom", "Other"];
|
|
39947
|
+
SOLUTION_KINDS = ["GoHighLevel build", "AI tool", "custom build", "process"];
|
|
39948
|
+
clean = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
39949
|
+
ANALYSIS_MIN_WORDS = 3;
|
|
39950
|
+
CLIENT_FILES_ROOT = ["Command OS", "Clients"];
|
|
39951
|
+
WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
39952
|
+
bullet = (xs) => xs.length ? xs.map((x) => `- ${x}`).join("\n") : "- (none)";
|
|
39953
|
+
q2 = (s) => `"${s.replace(/\s+/g, " ").trim()}"`;
|
|
39954
|
+
CONFIRM_INSTRUCTION = "Reply 'confirm all', or 'confirm all except \u2026' naming the ones to leave out.";
|
|
39955
|
+
stop = (s) => s && !/[.!?]["')\]]?$/.test(s) ? `${s}.` : s;
|
|
39956
|
+
EXISTING_WITH_CONTACT = "existing cannot be used with a contact: the contact's saved answers are the starting point, and only answers quoted from this call are added. Leave existing out and call again.";
|
|
39957
|
+
UNCLEAN_RECORD = "the newest saved assessment on this contact is incomplete or could not be read, so nothing was changed. It may have been edited by hand, or a save may have stopped partway; saving the assessment again with save_assessment writes a complete one, and then this call can be added.";
|
|
39958
|
+
contactLocks = /* @__PURE__ */ new Map();
|
|
39959
|
+
same = (a, b) => {
|
|
39960
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
39961
|
+
return [...keys].filter((k) => String(a[k] ?? "") !== String(b[k] ?? ""));
|
|
39962
|
+
};
|
|
39963
|
+
plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
39964
|
+
}
|
|
39965
|
+
});
|
|
39966
|
+
|
|
39457
39967
|
// src/tools/assessment.ts
|
|
39458
39968
|
function assessmentValidationMessage(error) {
|
|
39459
39969
|
if (!(error instanceof import_zod61.z.ZodError)) return void 0;
|
|
@@ -39536,19 +40046,34 @@ async function saveAssessment(client, locationId2, args, onOpportunityNoteFailur
|
|
|
39536
40046
|
const state = normalise(args.assessment);
|
|
39537
40047
|
const computed = computedFor(state);
|
|
39538
40048
|
const stage = stageFor({ verdict: computed.verdict, proposalSent: args.proposalSent, outcome: args.outcome });
|
|
39539
|
-
const { payload: contactBody, dedupable } = buildContact(state, stage, {
|
|
40049
|
+
const { payload: contactBody, dedupable: dedupableByDetails } = buildContact(state, stage, {
|
|
39540
40050
|
name: args.contactName,
|
|
39541
40051
|
email: args.email,
|
|
39542
40052
|
phone: args.phone
|
|
39543
40053
|
});
|
|
39544
|
-
|
|
39545
|
-
|
|
39546
|
-
|
|
39547
|
-
|
|
40054
|
+
let contactId;
|
|
40055
|
+
if (args.contactId) {
|
|
40056
|
+
const found = await client.get(`/contacts/${encodeURIComponent(args.contactId)}`);
|
|
40057
|
+
const existingContact = found?.contact;
|
|
40058
|
+
contactId = existingContact?.id;
|
|
40059
|
+
if (!contactId) {
|
|
40060
|
+
return { saved: false, error: `GoHighLevel has no contact ${args.contactId} in this account, so nothing was written.` };
|
|
40061
|
+
}
|
|
40062
|
+
if (!existingContact?.companyName?.trim() && contactBody.companyName) {
|
|
40063
|
+
await client.put(`/contacts/${encodeURIComponent(contactId)}`, { body: { companyName: contactBody.companyName } });
|
|
40064
|
+
}
|
|
40065
|
+
await client.post(`/contacts/${encodeURIComponent(contactId)}/tags`, { body: { tags: contactBody.tags } });
|
|
40066
|
+
} else {
|
|
40067
|
+
const upserted = await client.post("/contacts/upsert", {
|
|
40068
|
+
body: { ...contactBody, locationId: locationId2 }
|
|
40069
|
+
});
|
|
40070
|
+
contactId = upserted?.contact?.id ?? upserted?.id;
|
|
40071
|
+
}
|
|
39548
40072
|
if (!contactId) {
|
|
39549
40073
|
return { saved: false, error: "GoHighLevel did not return a contact id, so nothing was written. Nothing partial was left behind." };
|
|
39550
40074
|
}
|
|
39551
|
-
const
|
|
40075
|
+
const dedupable = dedupableByDetails || Boolean(args.contactId);
|
|
40076
|
+
const bodies = buildNoteBodies(buildNote(state, computed, LOOKUP, today(), args.source, args.confirmedByOwner));
|
|
39552
40077
|
const noteIds = [];
|
|
39553
40078
|
for (const body of bodies) {
|
|
39554
40079
|
const note = await client.post(`/contacts/${contactId}/notes`, { body: { body } });
|
|
@@ -39720,33 +40245,56 @@ function registerAssessmentTools(server2, client) {
|
|
|
39720
40245
|
safeTool(
|
|
39721
40246
|
server2,
|
|
39722
40247
|
"read_assessment_transcript",
|
|
39723
|
-
|
|
40248
|
+
`Bring a recorded client call (Plaud, Zoom, Pocket, Fathom, or any transcript file the owner attaches) into the client's assessment, their needs analysis and their client file. The flow: 1) get the transcript yourself, with your own Plaud or Zoom connection or from the attached file; 2) propose assessment answers, each with the client's words copied VERBATIM from the transcript as the quote; for a text question the value is the client's own words taken from that quote, and for a number it is the number they said, written in digits (Plaud writes "a hundred percent"; send "100%"); 3) write the needs analysis, every need, unnamed problem and recommended solution tied to a verbatim quote of at least three words; 4) call this with the client's contactId and the source; 5) write the returned clientFile to the owner's Google Drive with your Drive connection, following clientFile.howToWrite; 6) show the owner the reply as given, including the "To confirm" list. EVERY NUMBER waits for the owner's OK and never saves on its own, because no word check can tell a figure that answers the question from one said in passing. Text answers in the client's own words save straight away. When the owner answers the list ("confirm all", or "confirm all except ..."), call again with the same transcript, proposed and contactId, and confirmed holding exactly the approved items (questionId and value from toConfirm); for an item marked replacesSaved, also set replace: true on that proposed answer. Never put an item in confirmed that the owner has not approved in this conversation. Drive allows two folders with the same name, so SEARCH for each folder in the path (and for the client's folder above all) before creating it, and never create a second tree. To append to "Client understanding": read it, add the new dated section at the end, write it back, then read it again and confirm every earlier section is still there. Answers and analysis points whose quote is not in the transcript are thrown out. With a contactId the saved answers are read back: the reply starts SAVED, NOT SAVED, NOT CONFIRMED or NOTHING TO SAVE, and only SAVED means they are on the contact. The needs analysis is advice for the owner and is never sent to the client. Without a contactId nothing is saved.`,
|
|
39724
40249
|
{
|
|
39725
40250
|
transcript: import_zod61.z.string().min(20).describe("The recording, as text. Paste the whole thing."),
|
|
39726
40251
|
proposed: import_zod61.z.array(import_zod61.z.object({
|
|
39727
40252
|
questionId: import_zod61.z.string().describe("The assessment question this answers."),
|
|
39728
40253
|
value: import_zod61.z.string().describe("What to store, in the unit the question wants."),
|
|
39729
40254
|
quote: import_zod61.z.string().describe("The owner's own words, VERBATIM from the transcript. Do not tidy it up \u2014 if it is not in the transcript the answer is thrown out."),
|
|
39730
|
-
reasoning: import_zod61.z.string().optional().describe("How you got from the quote to the value, when they are not the same.")
|
|
40255
|
+
reasoning: import_zod61.z.string().optional().describe("How you got from the quote to the value, when they are not the same."),
|
|
40256
|
+
replace: import_zod61.z.boolean().optional().describe("Only after the owner confirmed it: let this answer replace a different one already saved on the contact.")
|
|
39731
40257
|
})).describe("One entry per question you could answer. Leave out anything the conversation did not cover."),
|
|
39732
|
-
|
|
40258
|
+
confirmed: import_zod61.z.array(import_zod61.z.object({
|
|
40259
|
+
questionId: import_zod61.z.string(),
|
|
40260
|
+
value: import_zod61.z.union([import_zod61.z.string(), import_zod61.z.number()]).describe("The exact value the owner said yes to.")
|
|
40261
|
+
})).optional().describe("Held answers the owner has said yes to, that specific value, in THIS conversation. Never add one the owner has not confirmed. Its quote must still be in the transcript, and it does not replace a different saved answer without replace: true."),
|
|
40262
|
+
existing: import_zod61.z.record(import_zod61.z.string(), import_zod61.z.union([import_zod61.z.string(), import_zod61.z.number()])).optional().describe("Answers already captured, so this adds to them rather than replacing them. Not with contactId: there the contact's saved answers are the starting point."),
|
|
40263
|
+
contactId: import_zod61.z.string().optional().describe("The client's GoHighLevel contact. Given, the checked answers are added to the assessment already saved on it and saved in this same call. Find it with search_contacts first."),
|
|
40264
|
+
locationId: import_zod61.z.string().optional().describe("The sub-account the contact is in. Defaults to the active one."),
|
|
40265
|
+
businessName: import_zod61.z.string().optional().describe("The client's business name, used when the contact and its saved assessment carry none."),
|
|
40266
|
+
createPipeline: import_zod61.z.boolean().optional().describe('Create the "Assessments" pipeline if it is missing. Only when the owner has said yes.'),
|
|
40267
|
+
source: import_zod61.z.object({
|
|
40268
|
+
recorder: import_zod61.z.enum(["Plaud", "Zoom", "Pocket", "Fathom", "Other"]).describe("What recorded the call."),
|
|
40269
|
+
recordedAt: import_zod61.z.string().describe("When the call happened, as an ISO date (2026-09-24) or timestamp, from the recording itself."),
|
|
40270
|
+
title: import_zod61.z.string().optional().describe("The recording's title, as the recorder shows it."),
|
|
40271
|
+
fetchedBy: import_zod61.z.enum(["claude", "upload"]).optional().describe("claude when you fetched it with your own connector, upload when the owner attached a file.")
|
|
40272
|
+
}).optional().describe("Where the transcript came from. Written onto the saved note and into the client file."),
|
|
40273
|
+
analysis: import_zod61.z.object({
|
|
40274
|
+
saidTheyWant: import_zod61.z.array(import_zod61.z.string()).optional().describe("What the client asked for, in plain words."),
|
|
40275
|
+
actuallyNeed: import_zod61.z.array(import_zod61.z.object({ point: import_zod61.z.string(), quote: import_zod61.z.string().describe("VERBATIM from the transcript.") })).optional().describe("What they really need, each with the words that show it."),
|
|
40276
|
+
unnamedProblems: import_zod61.z.array(import_zod61.z.object({ point: import_zod61.z.string(), quote: import_zod61.z.string().describe("VERBATIM from the transcript.") })).optional().describe("Problems they described without naming, each with the words that show it."),
|
|
40277
|
+
recommendedSolutions: import_zod61.z.array(import_zod61.z.object({
|
|
40278
|
+
solution: import_zod61.z.string(),
|
|
40279
|
+
kind: import_zod61.z.enum(["GoHighLevel build", "AI tool", "custom build", "process"]),
|
|
40280
|
+
why: import_zod61.z.string().describe("Why it fits this client."),
|
|
40281
|
+
quote: import_zod61.z.string().describe("VERBATIM: what they said that this answers.")
|
|
40282
|
+
})).optional().describe("What you would build or change, including things they did not ask for."),
|
|
40283
|
+
nextCallQuestions: import_zod61.z.array(import_zod61.z.string()).optional().describe("What to ask on the next call.")
|
|
40284
|
+
}).optional().describe("Your needs analysis of the call. For the owner only; it is never sent to the client. Every quote is checked against the transcript like the answers are.")
|
|
39733
40285
|
},
|
|
39734
|
-
async (
|
|
39735
|
-
const
|
|
39736
|
-
|
|
39737
|
-
|
|
39738
|
-
|
|
39739
|
-
|
|
39740
|
-
|
|
39741
|
-
|
|
39742
|
-
|
|
39743
|
-
|
|
39744
|
-
|
|
39745
|
-
|
|
39746
|
-
})),
|
|
39747
|
-
thrownOut: gated.rejected.map((r) => ({ questionId: r.questionId, value: r.value, why: r.because })),
|
|
39748
|
-
note: gated.rejected.some((r) => /does not appear in the transcript/.test(r.because)) ? "One or more answers cited words that are not in the transcript. Those were discarded, not stored. That check is the only thing standing between a recording and an invented figure in a client's report." : null
|
|
39749
|
-
};
|
|
40286
|
+
async (args) => {
|
|
40287
|
+
const [{ processClientCall: processClientCall2 }, { readAgencyProfile: readAgencyProfile2 }] = await Promise.all([
|
|
40288
|
+
Promise.resolve().then(() => (init_client_calls(), client_calls_exports)),
|
|
40289
|
+
Promise.resolve().then(() => (init_agency_profile(), agency_profile_exports))
|
|
40290
|
+
]);
|
|
40291
|
+
const wantsFiles = Boolean(args.contactId || args.analysis || args.source);
|
|
40292
|
+
return processClientCall2(args, {
|
|
40293
|
+
client,
|
|
40294
|
+
locationId: args.contactId ? client.resolveLocationId(args.locationId) : "",
|
|
40295
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
40296
|
+
clientFilesDir: wantsFiles ? readAgencyProfile2().clientFilesDir : void 0
|
|
40297
|
+
});
|
|
39750
40298
|
}
|
|
39751
40299
|
);
|
|
39752
40300
|
safeTool(
|
|
@@ -39770,10 +40318,10 @@ function registerAssessmentTools(server2, client) {
|
|
|
39770
40318
|
const r = compute(state.ans, state.skip, state.asm);
|
|
39771
40319
|
const labels = {};
|
|
39772
40320
|
for (const a of ASSUMPTIONS) labels[a[0]] = a[3];
|
|
39773
|
-
const answered = Q.filter((
|
|
39774
|
-
const id =
|
|
40321
|
+
const answered = Q.filter((q3) => {
|
|
40322
|
+
const id = q3[0];
|
|
39775
40323
|
return state.skip[id] === true || state.ans[id] !== void 0 && String(state.ans[id]).trim() !== "";
|
|
39776
|
-
}).map((
|
|
40324
|
+
}).map((q3) => state.skip[q3[0]] ? { ask: q3[2], unknown: true, unit: q3[4] } : { ask: q3[2], value: String(state.ans[q3[0]]), unit: q3[4] });
|
|
39777
40325
|
const html2 = renderReport2({
|
|
39778
40326
|
state,
|
|
39779
40327
|
computed: r,
|
|
@@ -39799,7 +40347,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
39799
40347
|
const dir = args.outDir ?? (0, import_node_path6.join)((0, import_node_os.homedir)(), "Documents", "assessments");
|
|
39800
40348
|
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
39801
40349
|
const slug3 = (state.biz || "assessment").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
39802
|
-
const file2 = (0, import_node_path6.join)(dir, `${slug3}-${(0,
|
|
40350
|
+
const file2 = (0, import_node_path6.join)(dir, `${slug3}-${(0, import_node_crypto11.randomBytes)(4).toString("hex")}.html`);
|
|
39803
40351
|
(0, import_node_fs6.writeFileSync)(file2, html2, "utf8");
|
|
39804
40352
|
return {
|
|
39805
40353
|
written: file2,
|
|
@@ -39822,7 +40370,7 @@ function registerAssessmentTools(server2, client) {
|
|
|
39822
40370
|
}
|
|
39823
40371
|
);
|
|
39824
40372
|
}
|
|
39825
|
-
var import_node_util3, import_zod61,
|
|
40373
|
+
var import_node_util3, import_zod61, import_node_crypto11, import_node_fs6, import_node_os, import_node_path6, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema;
|
|
39826
40374
|
var init_assessment = __esm({
|
|
39827
40375
|
"src/tools/assessment.ts"() {
|
|
39828
40376
|
"use strict";
|
|
@@ -39834,16 +40382,15 @@ var init_assessment = __esm({
|
|
|
39834
40382
|
init_tool_helpers();
|
|
39835
40383
|
init_store3();
|
|
39836
40384
|
init_engine();
|
|
39837
|
-
init_transcript();
|
|
39838
40385
|
init_report();
|
|
39839
|
-
|
|
40386
|
+
import_node_crypto11 = require("node:crypto");
|
|
39840
40387
|
import_node_fs6 = require("node:fs");
|
|
39841
40388
|
import_node_os = require("node:os");
|
|
39842
40389
|
import_node_path6 = require("node:path");
|
|
39843
|
-
QUESTION_UNITS = new Map(Q.map((
|
|
40390
|
+
QUESTION_UNITS = new Map(Q.map((q3) => [q3[0], q3[4]]));
|
|
39844
40391
|
AREA_TITLE = new Map(AREAS.map((a) => [a[0], a[1]]));
|
|
39845
|
-
ASK = new Map(Q.map((
|
|
39846
|
-
AREA_OF = new Map(Q.map((
|
|
40392
|
+
ASK = new Map(Q.map((q3) => [q3[0], q3[2]]));
|
|
40393
|
+
AREA_OF = new Map(Q.map((q3) => [q3[0], q3[1]]));
|
|
39847
40394
|
ASSUMPTION_LABEL = new Map(ASSUMPTIONS.map((a) => [a[0], a[1]]));
|
|
39848
40395
|
LOOKUP = {
|
|
39849
40396
|
ask: (id) => ASK.get(id),
|
|
@@ -39951,6 +40498,7 @@ __export(dashboard_exports, {
|
|
|
39951
40498
|
INTAKE_OWN_MESSAGES: () => INTAKE_OWN_MESSAGES,
|
|
39952
40499
|
INTAKE_UNREADABLE: () => INTAKE_UNREADABLE,
|
|
39953
40500
|
NOT_SIGNED_IN_MSG: () => NOT_SIGNED_IN_MSG,
|
|
40501
|
+
PARKED_NOT_BUILT: () => PARKED_NOT_BUILT,
|
|
39954
40502
|
SEAT_GUARDED_READS: () => SEAT_GUARDED_READS,
|
|
39955
40503
|
SEAT_TOKEN_HEADER: () => SEAT_TOKEN_HEADER,
|
|
39956
40504
|
STAGES: () => STAGES2,
|
|
@@ -40178,11 +40726,11 @@ function startRun(locationId2, stage, onDone, meta = {}) {
|
|
|
40178
40726
|
industry,
|
|
40179
40727
|
manualObligations: stage === 4 ? readBuildObligations(locationId2) : void 0,
|
|
40180
40728
|
intakeSummary: describeComposition({ industry, agency }),
|
|
40181
|
-
customLabels: composed.filter((
|
|
40182
|
-
const base = INTAKE_QUESTIONS.find((b) => b.key ===
|
|
40183
|
-
return base && base.label !==
|
|
40184
|
-
}).map((
|
|
40185
|
-
removedKeys: INTAKE_QUESTIONS.filter((b) => !composed.some((
|
|
40729
|
+
customLabels: composed.filter((q3) => {
|
|
40730
|
+
const base = INTAKE_QUESTIONS.find((b) => b.key === q3.key);
|
|
40731
|
+
return base && base.label !== q3.label;
|
|
40732
|
+
}).map((q3) => ({ key: q3.key, label: q3.label, from: INTAKE_QUESTIONS.find((b) => b.key === q3.key)?.label })),
|
|
40733
|
+
removedKeys: INTAKE_QUESTIONS.filter((b) => !composed.some((q3) => q3.key === b.key)).map((b) => b.key),
|
|
40186
40734
|
// A build re-run reuses the approved plan already saved for this account (finding 10).
|
|
40187
40735
|
// ...and only while it still builds the scope the member has confirmed now: a
|
|
40188
40736
|
// changed selection composes the plan fresh from the preset (delta round 3, T-M4).
|
|
@@ -41327,6 +41875,9 @@ function preflightCard(c){const box=document.createElement("section");box.classN
|
|
|
41327
41875
|
const a=document.createElement("a");a.href="/agency#connections";a.textContent="Open Your agency";box.appendChild(a);return box}
|
|
41328
41876
|
function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES.map(()=>"pending")}).stages;
|
|
41329
41877
|
const sandbox=typeOf(state,c.locationId)==="sandbox";
|
|
41878
|
+
// A template or hidden account opens only so its owner can lock or unlock it and change its
|
|
41879
|
+
// type: nothing on its card builds into it (review L-1; a template can be the live snapshot source).
|
|
41880
|
+
const parkedType=["template","hidden"].includes(typeOf(state,c.locationId));
|
|
41330
41881
|
const div=document.createElement("div");div.className="client";
|
|
41331
41882
|
const head=document.createElement("div");head.className="client-head";
|
|
41332
41883
|
const av=document.createElement("span");av.className="avatar lg";av.textContent=initials(c.name);head.appendChild(av);
|
|
@@ -41344,7 +41895,8 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
|
|
|
41344
41895
|
isel.onchange=async()=>{await postJSON("/api/industry",{locationId:c.locationId,industry:isel.value});load()};
|
|
41345
41896
|
sels.appendChild(isel);head.appendChild(sels);div.appendChild(head);
|
|
41346
41897
|
const costs=(state.costRecords||[]).filter(r=>r.locationId===c.locationId);if(costs.length){const details=document.createElement("details");const title=document.createElement("summary");title.textContent="Claude usage for this client";details.appendChild(title);for(const cost of costs){const p=document.createElement("p");p.textContent=costLabel(cost.kind)+": "+(typeof cost.costUsd==="number"?"$"+cost.costUsd.toFixed(4):"cost not reported")+". "+new Date(cost.at).toLocaleString();details.appendChild(p)}div.appendChild(details)}
|
|
41347
|
-
if(
|
|
41898
|
+
if(parkedType){const pn=document.createElement("p");pn.className="muted";pn.textContent="This is a "+typeOf(state,c.locationId)+" account, so nothing is built into it from here. You can lock or unlock it below, or change its type to Client to build it.";div.appendChild(pn)}
|
|
41899
|
+
else if(!st.some(s=>s==="done"))div.appendChild(preflightCard(c));
|
|
41348
41900
|
const approveBuilding=async()=>{const typed=prompt('Approve the Blueprint Review for "'+c.name+'" and let Command OS build in this account.'+String.fromCharCode(10,10)+'Type the client name exactly to confirm:');if(typed===null)return;
|
|
41349
41901
|
try{const response=await fetch("/api/build-approval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,confirmName:typed})});const r=await response.json();
|
|
41350
41902
|
if(!response.ok||r.ok!==true){r.ok=false;r.error=r.error||"Command OS could not confirm the result. Refresh this client to read its saved state before retrying."}
|
|
@@ -41390,8 +41942,8 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
|
|
|
41390
41942
|
el.appendChild(note)}
|
|
41391
41943
|
if(POWERED.includes(i)&&(st[i]||"pending")!=="done"){el.style.cursor="default";
|
|
41392
41944
|
el.title="Command OS runs and checks this step. Its result comes from a run, never from a click."}
|
|
41393
|
-
if(gate&&!anyBusy){const action=document.createElement("button");action.className="intakebtn";action.textContent=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};el.appendChild(action)}
|
|
41394
|
-
const canBuild
|
|
41945
|
+
if(gate&&!anyBusy&&!parkedType){const action=document.createElement("button");action.className="intakebtn";action.textContent=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};el.appendChild(action)}
|
|
41946
|
+
const canBuild=!parkedType&&(i<=1||sandbox||Boolean((state.buildApproved||{})[c.locationId]));
|
|
41395
41947
|
if(canBuild&&POWERED.includes(i)){const b=document.createElement("button");b.className="runbtn";
|
|
41396
41948
|
const busy=run&&run.running;b.textContent=busy&&run.locationId===c.locationId&&run.stage===i?"Running\u2026":"Run this step";
|
|
41397
41949
|
b.disabled=Boolean(busy)||(!st.some(s=>s==="done")&&!preflights[c.locationId]?.ok);
|
|
@@ -41412,7 +41964,7 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
|
|
|
41412
41964
|
for(const stale of view.stale){const p=document.createElement("p");p.textContent=LABELS[stale.stage]+": stale. "+stale.reason;div.appendChild(p)}
|
|
41413
41965
|
if(view.approvedAt){const p=document.createElement("p");p.textContent="Building approved on "+new Date(view.approvedAt).toLocaleString()+".";div.appendChild(p)}
|
|
41414
41966
|
for(const repair of view.repairs){const p=document.createElement("p");p.textContent="Live workflow repair: "+repair.name+". "+repair.changes.join(" ");div.appendChild(p)}
|
|
41415
|
-
if(view.planHash){const approve=document.createElement("button");approve.textContent="Approve building\u2026";approve.onclick=async()=>{const typed=sandbox?c.name:prompt("Approve building and the listed live workflow repairs for "+c.name+". Type the client name to confirm.");if(typed===null)return;if(await postJSON("/api/build-approval",{locationId:c.locationId,confirmName:typed,repairHashes:view.repairs.map(r=>r.hash)}))load()};div.appendChild(approve)}
|
|
41967
|
+
if(view.planHash&&!parkedType){const approve=document.createElement("button");approve.textContent="Approve building\u2026";approve.onclick=async()=>{const typed=sandbox?c.name:prompt("Approve building and the listed live workflow repairs for "+c.name+". Type the client name to confirm.");if(typed===null)return;if(await postJSON("/api/build-approval",{locationId:c.locationId,confirmName:typed,repairHashes:view.repairs.map(r=>r.hash)}))load()};div.appendChild(approve)}
|
|
41416
41968
|
}else{const p=document.createElement("p");p.textContent="The executable plan is prepared when Build runs.";div.appendChild(p)}
|
|
41417
41969
|
if(view?.handoff)div.appendChild(handoffPanel(c,view));
|
|
41418
41970
|
const _prot=PROTECTED[c.locationId];
|
|
@@ -41429,6 +41981,7 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
|
|
|
41429
41981
|
const rv=document.createElement("button");rv.className="qdel";rv.style.marginLeft=".5rem";rv.textContent="revoke";
|
|
41430
41982
|
rv.onclick=async()=>{await postJSON("/api/build-approval",{locationId:c.locationId,revoke:true});load()};
|
|
41431
41983
|
ab.appendChild(s1);ab.appendChild(rv);ab.appendChild(protectBtn(c))}
|
|
41984
|
+
else if(parkedType){ab.appendChild(protectBtn(c))}
|
|
41432
41985
|
else{const w=document.createElement("span");w.className="muted";w.textContent="Automated building is off for this client. ";
|
|
41433
41986
|
const btn=document.createElement("button");btn.className="intakebtn";btn.style.padding=".25rem .55rem";btn.textContent="Approve building\u2026";
|
|
41434
41987
|
btn.onclick=approveBuilding;
|
|
@@ -41809,10 +42362,12 @@ function render(state,run){if(state.roster){ROSTER.splice(0,ROSTER.length,...sta
|
|
|
41809
42362
|
const parked=sorted.filter(c=>match(c)&&["template","hidden"].includes(typeOf(state,c.locationId)));
|
|
41810
42363
|
if(!board.length&&!parked.length){const d=document.createElement("div");d.className="client";d.innerHTML="<h2>No matches</h2>";m.appendChild(d);return}
|
|
41811
42364
|
/* One card open at a time. Default: the account being built, else the busiest one. */
|
|
41812
|
-
|
|
42365
|
+
/* A template or hidden account opens too: its card carries the lock, and an owner must never be locked out of their own account (BUG-2026-09-24-07). */
|
|
42366
|
+
const openable=[...board,...parked];
|
|
42367
|
+
let act=(activeId&&openable.some(c=>c.locationId===activeId))?activeId:null;
|
|
41813
42368
|
if(!act&&run&&run.running&&board.some(c=>c.locationId===run.locationId))act=run.locationId;
|
|
41814
42369
|
if(!act&&board.length)act=board[0].locationId;
|
|
41815
|
-
const actC=
|
|
42370
|
+
const actC=openable.find(c=>c.locationId===act);
|
|
41816
42371
|
if(run&&run.running&&actC&&run.locationId!==actC.locationId){
|
|
41817
42372
|
const other=ROSTER.find(c=>c.locationId===run.locationId);
|
|
41818
42373
|
if(other){const ban=document.createElement("div");ban.className="card";ban.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.55rem .9rem;margin-bottom:1rem;border-color:var(--gold)";
|
|
@@ -41830,11 +42385,12 @@ function render(state,run){if(state.roster){ROSTER.splice(0,ROSTER.length,...sta
|
|
|
41830
42385
|
const hint=document.createElement("p");hint.className="switchnote";
|
|
41831
42386
|
hint.textContent="One account at a time. Opening another puts this one back in the list. Nothing is lost.";
|
|
41832
42387
|
m.appendChild(hint)}
|
|
41833
|
-
|
|
41834
|
-
|
|
42388
|
+
const parkedRest=parked.filter(c=>c.locationId!==act);
|
|
42389
|
+
if(parkedRest.length){const gh=document.createElement("div");gh.className="grouphead";
|
|
42390
|
+
gh.textContent=(showParked?"\u25BE":"\u25B8")+" Templates & hidden ("+parkedRest.length+")";
|
|
41835
42391
|
gh.onclick=()=>{showParked=!showParked;render(state,run)};m.appendChild(gh);
|
|
41836
42392
|
if(showParked){const list2=document.createElement("div");list2.className="card acct-list";
|
|
41837
|
-
for(const c of
|
|
42393
|
+
for(const c of parkedRest)list2.appendChild(acctRow(c,state,run));m.appendChild(list2)}}}
|
|
41838
42394
|
${COCKPIT_TYPE_JS}
|
|
41839
42395
|
load();
|
|
41840
42396
|
</script></body></html>`;
|
|
@@ -42552,6 +43108,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42552
43108
|
try {
|
|
42553
43109
|
const { locationId: locationId2, stage, intakeConfirmed } = JSON.parse(body);
|
|
42554
43110
|
if (activeRun2?.running) throw new Error("a run is already in progress");
|
|
43111
|
+
const runType = readCockpitState().accounts?.[locationId2]?.type;
|
|
43112
|
+
if (runType === "template" || runType === "hidden") throw new Error(PARKED_NOT_BUILT);
|
|
42555
43113
|
let st0 = readCockpitState();
|
|
42556
43114
|
let inputHash;
|
|
42557
43115
|
let normalizedAnswers;
|
|
@@ -42580,8 +43138,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42580
43138
|
const snap = await readIntakeSnapshot(c, locationId2);
|
|
42581
43139
|
if (snap.status === "unavailable") throw new Error(INTAKE_UNREADABLE);
|
|
42582
43140
|
if (snap.status !== "received") throw new Error("Open the real form, submit it, then refresh.");
|
|
42583
|
-
const required = composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }).filter((
|
|
42584
|
-
if (required.length) throw new Error("Complete these required intake answers: " + required.map((
|
|
43141
|
+
const required = composeQuestionSet({ industry: st0.accounts?.[locationId2]?.industry, agency: readOverlay() }).filter((q3) => q3.required && !String(normalizedAnswers?.[q3.label] ?? normalizedAnswers?.[q3.key] ?? "").trim());
|
|
43142
|
+
if (required.length) throw new Error("Complete these required intake answers: " + required.map((q3) => q3.label).join(", ") + ".");
|
|
42585
43143
|
}
|
|
42586
43144
|
if (stage === 0 && intakeConfirmed !== true) throw new Error("Confirm the custom fields, form and published notification workflow on this client\u2019s Intake button.");
|
|
42587
43145
|
if (!st0.accounts?.[locationId2]?.industry) throw new Error("Choose the client industry before running Intake.");
|
|
@@ -42627,7 +43185,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42627
43185
|
industries: INDUSTRY_PACKS.map((p) => ({ slug: p.slug, label: p.label })),
|
|
42628
43186
|
overlay: agency,
|
|
42629
43187
|
warnings: unknownKeys(agency, INTAKE_QUESTIONS).map((k) => `Your overlay references "${k}", which isn't in the base intake.`),
|
|
42630
|
-
questions: composed.map((
|
|
43188
|
+
questions: composed.map((q3) => ({ key: q3.key, label: q3.label, sectionLabel: q3.sectionLabel, required: q3.required, base: INTAKE_QUESTIONS.some((b) => b.key === q3.key) }))
|
|
42631
43189
|
}));
|
|
42632
43190
|
return;
|
|
42633
43191
|
}
|
|
@@ -42663,14 +43221,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42663
43221
|
const snap = c ? await readIntakeSnapshot(c, locationId2, Boolean(st.intakeSent?.[locationId2])) : { status: "not_installed", fields: {}, queryKeys: {}, submissions: 0 };
|
|
42664
43222
|
if (snap.status === "unavailable") throw new Error(INTAKE_UNREADABLE);
|
|
42665
43223
|
const industry = st.accounts?.[locationId2]?.industry;
|
|
42666
|
-
const questions = composeQuestionSet({ industry, agency: readOverlay() }).map((
|
|
43224
|
+
const questions = composeQuestionSet({ industry, agency: readOverlay() }).map((q3) => ({ key: q3.key, contactTag: q3.field.kind === "standard" ? q3.field.tag : void 0, label: q3.label, sectionLabel: q3.sectionLabel, required: q3.required, options: q3.options ?? null, placeholder: q3.placeholder ?? "", multi: q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS" }));
|
|
42667
43225
|
const answers = normalizedIntake(snap, st.intakeDrafts?.[locationId2]);
|
|
42668
43226
|
const submittedAnswers = normalizedIntake(snap);
|
|
42669
43227
|
const hash = intakeInput(submittedAnswers, industry, composeQuestionSet({ industry, agency: readOverlay() }));
|
|
42670
43228
|
const latestState = readCockpitState();
|
|
42671
43229
|
writeCockpitState({ ...latestState, intakeInputs: { ...latestState.intakeInputs, [locationId2]: { hash, answers: submittedAnswers, status: snap.status } } });
|
|
42672
43230
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
42673
|
-
res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((
|
|
43231
|
+
res.end(JSON.stringify({ ok: true, ...snap, questions, industry, inputHash: hash, unansweredRequired: questions.filter((q3) => q3.required && !String(answers[q3.label] ?? answers[q3.key] ?? "").trim()).map((q3) => q3.label), costs: readRunCosts().filter((r) => r.locationId === locationId2 && (r.kind === "intake extraction" || r.kind === "engine-check")).slice(-10), submittedAnswers: snap.answers ?? {}, answers, savedAt: st.intakeDrafts?.[locationId2]?.savedAt, connectionPending: !c }));
|
|
42674
43232
|
} catch (e) {
|
|
42675
43233
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
42676
43234
|
logQuietly(`client-intake ${new URL(req.url || "/", "http://x").searchParams.get("locationId") || ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -42689,7 +43247,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42689
43247
|
const snap = await readIntakeSnapshot(c, args.locationId, false);
|
|
42690
43248
|
if (!snap.formId || !snap.formUrl) throw new Error(INTAKE_FORM_REQUIRED);
|
|
42691
43249
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
42692
|
-
const identityLabels = new Set(composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).filter((
|
|
43250
|
+
const identityLabels = new Set(composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).filter((q3) => q3.field.kind === "standard").map((q3) => q3.label));
|
|
42693
43251
|
const customAnswers = Object.fromEntries(Object.entries(args.answers ?? {}).filter(([label]) => !identityLabels.has(label)));
|
|
42694
43252
|
const pf = prefillFormUrl(snap.formUrl, snap.queryKeys, customAnswers, args.contact ?? {}, snap.altQueryKeys ?? {});
|
|
42695
43253
|
res.end(JSON.stringify({ ok: true, url: pf.url, leftovers: pf.leftovers }));
|
|
@@ -42741,18 +43299,18 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42741
43299
|
const source = fileSource ? fileSource : args.url ? { kind: "url", url: String(args.url) } : args.instruction ? { kind: "recorder", instruction: String(args.instruction) } : { kind: "text", text: String(args.transcript ?? "") };
|
|
42742
43300
|
let recorderPatterns = [];
|
|
42743
43301
|
if (source.kind === "recorder") {
|
|
42744
|
-
recorderPatterns = await new Promise((
|
|
43302
|
+
recorderPatterns = await new Promise((resolve13) => {
|
|
42745
43303
|
const mcpList = claudeCommand(claudeBin(), ["mcp", "list"]);
|
|
42746
43304
|
const ls = (0, import_child_process5.spawn)(mcpList.command, mcpList.args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
42747
43305
|
let buf = "";
|
|
42748
43306
|
ls.stdout.on("data", (d) => {
|
|
42749
43307
|
buf += d.toString();
|
|
42750
43308
|
});
|
|
42751
|
-
ls.on("close", () =>
|
|
42752
|
-
ls.on("error", () =>
|
|
43309
|
+
ls.on("close", () => resolve13(recorderAllowPatterns(buf)));
|
|
43310
|
+
ls.on("error", () => resolve13([]));
|
|
42753
43311
|
setTimeout(() => {
|
|
42754
43312
|
ls.kill("SIGKILL");
|
|
42755
|
-
|
|
43313
|
+
resolve13(recorderAllowPatterns(buf));
|
|
42756
43314
|
}, 9e4).unref?.();
|
|
42757
43315
|
});
|
|
42758
43316
|
if (!recorderPatterns.length) {
|
|
@@ -42763,10 +43321,10 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
42763
43321
|
}
|
|
42764
43322
|
const out = await prefillFromSource(
|
|
42765
43323
|
(prompt, tools) => callClaudeRecorded(bin, ["-p", prompt, "--max-turns", "25", "--disallowedTools", tools.deny, "--allowedTools", tools.allow || ""], { locationId: args.locationId, stage: 0, kind: "intake extraction" }),
|
|
42766
|
-
composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((
|
|
43324
|
+
composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q3) => q3.label),
|
|
42767
43325
|
source,
|
|
42768
43326
|
recorderPatterns,
|
|
42769
|
-
composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((
|
|
43327
|
+
composeQuestionSet({ industry: readCockpitState().accounts?.[args.locationId]?.industry, agency: readOverlay() }).map((q3) => ({ label: q3.label, options: q3.options, multi: q3.field.kind === "custom" && q3.field.dataType === "MULTIPLE_OPTIONS", dataType: q3.field.kind === "custom" ? q3.field.dataType : q3.field.tag }))
|
|
42770
43328
|
);
|
|
42771
43329
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
42772
43330
|
res.end(JSON.stringify({ ...out, costs: readRunCosts().filter((r) => r.locationId === args.locationId).slice(-2) }));
|
|
@@ -43086,8 +43644,8 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
43086
43644
|
return;
|
|
43087
43645
|
}
|
|
43088
43646
|
if (req.method === "GET" && url === "/blueprint") {
|
|
43089
|
-
const
|
|
43090
|
-
const locationId2 =
|
|
43647
|
+
const q3 = new URL(req.url || "/", "http://x").searchParams;
|
|
43648
|
+
const locationId2 = q3.get("locationId") || "";
|
|
43091
43649
|
const st = readCockpitState();
|
|
43092
43650
|
const review = st.reviews?.[locationId2];
|
|
43093
43651
|
if (!review?.ok) {
|
|
@@ -43097,7 +43655,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
43097
43655
|
}
|
|
43098
43656
|
const name = currentRoster().find((r) => r.locationId === locationId2)?.name || locationId2;
|
|
43099
43657
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
43100
|
-
res.end(reviewDocumentHtml(review, { scope: documentScopeFor(st, locationId2), assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: assessmentPurchasesFor(st, locationId2), planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName:
|
|
43658
|
+
res.end(reviewDocumentHtml(review, { scope: documentScopeFor(st, locationId2), assessment: st.assessmentAttachments?.[locationId2], purchasedFeatureIds: assessmentPurchasesFor(st, locationId2), planLines: readSavedPlan(locationId2) ? executablePlanLines(readSavedPlan(locationId2)) : [], clientName: name, agencyName: q3.get("agency") || st.agencyName, accent: q3.get("accent") || st.agencyAccent }));
|
|
43101
43659
|
return;
|
|
43102
43660
|
}
|
|
43103
43661
|
if (req.method === "GET" && url === "/api/review") {
|
|
@@ -43558,9 +44116,9 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
43558
44116
|
res.end();
|
|
43559
44117
|
}));
|
|
43560
44118
|
try {
|
|
43561
|
-
await new Promise((
|
|
44119
|
+
await new Promise((resolve13, reject) => {
|
|
43562
44120
|
server2.once("error", reject);
|
|
43563
|
-
server2.listen(port, "127.0.0.1",
|
|
44121
|
+
server2.listen(port, "127.0.0.1", resolve13);
|
|
43564
44122
|
});
|
|
43565
44123
|
} catch (e) {
|
|
43566
44124
|
const msg3 = e?.code === "EADDRINUSE" ? `Port ${port} is busy with another program. Close it, or start Command OS on a different port.` : `Command OS could not start: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -43586,13 +44144,13 @@ ${opportunityNoteFailure?.detail ?? ""}`);
|
|
|
43586
44144
|
if (adopted) process.stderr.write(` Synced ${adopted} client${adopted === 1 ? "" : "s"} from your GHL (teammate updates).
|
|
43587
44145
|
`);
|
|
43588
44146
|
});
|
|
43589
|
-
return await new Promise((
|
|
43590
|
-
const
|
|
43591
|
-
process.on("SIGINT",
|
|
43592
|
-
process.on("SIGTERM",
|
|
44147
|
+
return await new Promise((resolve13) => {
|
|
44148
|
+
const stop2 = () => server2.close(() => resolve13(0));
|
|
44149
|
+
process.on("SIGINT", stop2);
|
|
44150
|
+
process.on("SIGTERM", stop2);
|
|
43593
44151
|
});
|
|
43594
44152
|
}
|
|
43595
|
-
var import_zod62, import_node_util4, fs35, path33, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, ConnectionRequest, DisconnectRequest;
|
|
44153
|
+
var import_zod62, import_node_util4, fs35, path33, http, import_child_process4, import_child_process5, osmod, STAGES2, ACCOUNT_TYPES, activeRun2, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, INTAKE_UNREADABLE, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, BOARD_ROUTES, BOARD_TITLES, anthropicKeyWrites, SEAT_TOKEN_HEADER, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, ConnectionRequest, DisconnectRequest;
|
|
43596
44154
|
var init_dashboard = __esm({
|
|
43597
44155
|
"src/dashboard.ts"() {
|
|
43598
44156
|
"use strict";
|
|
@@ -43699,6 +44257,7 @@ var init_dashboard = __esm({
|
|
|
43699
44257
|
UPGRADE_MSG = "\n The cockpit is not included in this licence, so it did not start.\n Nothing was changed.\n\n";
|
|
43700
44258
|
DOC_EXTS = /* @__PURE__ */ new Set([".md", ".txt", ".vtt", ".srt", ".csv"]);
|
|
43701
44259
|
DOC_MAX_BYTES = 20 * 1024 * 1024;
|
|
44260
|
+
PARKED_NOT_BUILT = "Template and hidden accounts are not built from Command OS. Change the account's type to Client first.";
|
|
43702
44261
|
NEEDS_AGENCY_KEY = "No agency key on file. Run register_agency_key in GHL Command first.";
|
|
43703
44262
|
INTAKE_UNREADABLE = "Command OS could not read this client's account. Paste its key again in Build a Client Account.";
|
|
43704
44263
|
INTAKE_FORM_REQUIRED = "Run the Intake stage in Client Builds to create the form, then come back and submit.";
|
|
@@ -43727,8 +44286,8 @@ var init_dashboard = __esm({
|
|
|
43727
44286
|
NOT_SIGNED_IN_MSG = "This browser isn't signed in to Command OS on this computer. Open Command OS on the machine it runs on, and use the page it opens for you.";
|
|
43728
44287
|
SEAT_GUARDED_READS = ["/api/brief", "/api/brief/panel", "/api/connections", "/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight"];
|
|
43729
44288
|
ConnectionRequest = import_zod62.z.discriminatedUnion("system", [
|
|
43730
|
-
import_zod62.z.object({ system: import_zod62.z.literal("stripe"), key: import_zod62.z.string().min(8), label: import_zod62.z.string().optional() }).strict(),
|
|
43731
|
-
import_zod62.z.object({ system: import_zod62.z.literal("slack"), key: import_zod62.z.string().min(8), label: import_zod62.z.string().optional() }).strict(),
|
|
44289
|
+
import_zod62.z.object({ system: import_zod62.z.literal("stripe"), key: import_zod62.z.string().min(8).max(4096), label: import_zod62.z.string().optional() }).strict(),
|
|
44290
|
+
import_zod62.z.object({ system: import_zod62.z.literal("slack"), key: import_zod62.z.string().min(8).max(4096), label: import_zod62.z.string().optional() }).strict(),
|
|
43732
44291
|
import_zod62.z.object({ system: import_zod62.z.literal("google-calendar"), action: import_zod62.z.enum(["start", "finish", "status"]).default("start") }).strict()
|
|
43733
44292
|
]);
|
|
43734
44293
|
DisconnectRequest = import_zod62.z.object({ system: import_zod62.z.enum(["stripe", "slack", "google-calendar"]) }).strict();
|
|
@@ -44190,12 +44749,12 @@ ${companies.map((c) => ` ${c.name || c.companyId} (${c.companyId})`).join("\n")
|
|
|
44190
44749
|
]
|
|
44191
44750
|
};
|
|
44192
44751
|
}
|
|
44193
|
-
const
|
|
44752
|
+
const tokens2 = registry2.listLocations().map((loc) => ({
|
|
44194
44753
|
...loc,
|
|
44195
44754
|
companyId: registry2.getToken(loc.locationId)?.companyId
|
|
44196
44755
|
}));
|
|
44197
44756
|
const currentId = client.defaultLocationId;
|
|
44198
|
-
const lines =
|
|
44757
|
+
const lines = tokens2.map((loc) => {
|
|
44199
44758
|
const marker = loc.locationId === currentId ? "\u2192 " : " ";
|
|
44200
44759
|
const company = loc.companyId ? ` [company ${loc.companyId}${companiesWithFirebase.has(loc.companyId) ? ", Firebase \u2713" : ", no Firebase"}]` : "";
|
|
44201
44760
|
return `${marker}${loc.name} (${loc.locationId})${company}`;
|
|
@@ -44208,7 +44767,7 @@ ${companies.map((c) => ` ${c.name || c.companyId} (${c.companyId})`).join("\n")
|
|
|
44208
44767
|
content: [
|
|
44209
44768
|
{
|
|
44210
44769
|
type: "text",
|
|
44211
|
-
text: `Token Registry (${
|
|
44770
|
+
text: `Token Registry (${tokens2.length} locations):
|
|
44212
44771
|
${lines.join("\n")}
|
|
44213
44772
|
|
|
44214
44773
|
\u2192 = currently active${fbSection}`
|
|
@@ -44363,7 +44922,7 @@ var init_location_switcher = __esm({
|
|
|
44363
44922
|
|
|
44364
44923
|
// src/tools/bulk-operations.ts
|
|
44365
44924
|
function delay2(ms) {
|
|
44366
|
-
return new Promise((
|
|
44925
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
44367
44926
|
}
|
|
44368
44927
|
function formatResults(op, results, total) {
|
|
44369
44928
|
return `${op}: ${results.success} success, ${results.failed} failed out of ${total}.${results.errors.length ? "\nErrors:\n" + results.errors.join("\n") : ""}`;
|
|
@@ -44608,7 +45167,7 @@ var init_asset_ownership = __esm({
|
|
|
44608
45167
|
function sectionItems(section2) {
|
|
44609
45168
|
return Array.isArray(section2) ? section2 : [];
|
|
44610
45169
|
}
|
|
44611
|
-
function
|
|
45170
|
+
function bullet2(lines) {
|
|
44612
45171
|
return lines.length ? lines.map((l) => `- ${l}`).join("\n") : "- None found.";
|
|
44613
45172
|
}
|
|
44614
45173
|
function plainReason(reason3) {
|
|
@@ -44630,7 +45189,7 @@ function sectionList(section2, render) {
|
|
|
44630
45189
|
if (!Array.isArray(section2)) {
|
|
44631
45190
|
return `**Not read.** ${plainReason(section2.unavailable)}. This section is unknown, which is NOT the same as empty. Check it by hand before you rely on this document.`;
|
|
44632
45191
|
}
|
|
44633
|
-
return
|
|
45192
|
+
return bullet2(section2.map(render));
|
|
44634
45193
|
}
|
|
44635
45194
|
function buildHandoffDoc(input) {
|
|
44636
45195
|
const {
|
|
@@ -44652,13 +45211,13 @@ function buildHandoffDoc(input) {
|
|
|
44652
45211
|
if (!items.length) return "";
|
|
44653
45212
|
return `**${kind}** ${OWNER_MEANING[kind]}
|
|
44654
45213
|
|
|
44655
|
-
${
|
|
45214
|
+
${bullet2(
|
|
44656
45215
|
items.map((i) => i.note ? `${i.asset} (${i.note})` : i.asset)
|
|
44657
45216
|
)}`;
|
|
44658
45217
|
}).filter(Boolean).join("\n\n") : "Nothing has been recorded yet. Every asset below is unanswered.";
|
|
44659
45218
|
const unknownSection = ownership.unknown.length ? `These exist in the account but nobody has said who owns them. Answer them once and they stay answered:
|
|
44660
45219
|
|
|
44661
|
-
${
|
|
45220
|
+
${bullet2(ownership.unknown)}` : "Every asset found in this account has a recorded owner.";
|
|
44662
45221
|
const webhookSection = sectionList(webhooks, (w) => `${w.name} sends to ${w.url}${w.events.length ? ` when: ${w.events.join(", ")}` : ""}`) + (webhookScanPartial ? `
|
|
44663
45222
|
|
|
44664
45223
|
**This list may be incomplete.** ${webhookScanPartial}` : "");
|
|
@@ -44735,7 +45294,7 @@ ${ownership.unreadable.length ? `
|
|
|
44735
45294
|
|
|
44736
45295
|
Somebody edited these by hand and the answer no longer parses. They need a human:
|
|
44737
45296
|
|
|
44738
|
-
${
|
|
45297
|
+
${bullet2(ownership.unreadable)}
|
|
44739
45298
|
` : ""}
|
|
44740
45299
|
## Rotate these on the way out
|
|
44741
45300
|
|
|
@@ -44768,7 +45327,7 @@ var init_handoff_doc = __esm({
|
|
|
44768
45327
|
|
|
44769
45328
|
// src/tools/account-export.ts
|
|
44770
45329
|
function delay3(ms) {
|
|
44771
|
-
return new Promise((
|
|
45330
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
44772
45331
|
}
|
|
44773
45332
|
function builderSectionAvailability(builderLocationId, requestedLocationId) {
|
|
44774
45333
|
if (!builderLocationId) {
|
|
@@ -45780,7 +46339,7 @@ ${text3}`);
|
|
|
45780
46339
|
);
|
|
45781
46340
|
}
|
|
45782
46341
|
function buildReviewsQuery(locationId2, opts = {}) {
|
|
45783
|
-
const
|
|
46342
|
+
const q3 = [
|
|
45784
46343
|
`filterParams[locationId][0][value]=${locationId2}`,
|
|
45785
46344
|
`filterParams[locationId][0][condition]=eq`,
|
|
45786
46345
|
`filterParams[deleted][0][value]=${opts.includeDeleted ? "true" : "false"}`,
|
|
@@ -45794,13 +46353,13 @@ function buildReviewsQuery(locationId2, opts = {}) {
|
|
|
45794
46353
|
if (opts.maxRating !== void 0) bounds.push([opts.maxRating, "lte"]);
|
|
45795
46354
|
}
|
|
45796
46355
|
bounds.forEach(([value, condition], i) => {
|
|
45797
|
-
|
|
45798
|
-
|
|
46356
|
+
q3.push(`filterParams[starRating][${i}][value]=${value}`);
|
|
46357
|
+
q3.push(`filterParams[starRating][${i}][condition]=${condition}`);
|
|
45799
46358
|
});
|
|
45800
|
-
|
|
45801
|
-
|
|
45802
|
-
|
|
45803
|
-
const query =
|
|
46359
|
+
q3.push(`sortParams[dateAdded]=-1`);
|
|
46360
|
+
q3.push(`pageNumber=${opts.pageNumber ?? 1}`);
|
|
46361
|
+
q3.push(`pageSize=${opts.pageSize ?? 10}`);
|
|
46362
|
+
const query = q3.map((p) => {
|
|
45804
46363
|
const i = p.indexOf("=");
|
|
45805
46364
|
return `${encodeURIComponent(p.slice(0, i))}=${encodeURIComponent(p.slice(i + 1))}`;
|
|
45806
46365
|
}).join("&");
|
|
@@ -46066,7 +46625,7 @@ var init_memberships = __esm({
|
|
|
46066
46625
|
|
|
46067
46626
|
// src/tools/template-deployer.ts
|
|
46068
46627
|
function delay4(ms) {
|
|
46069
|
-
return new Promise((
|
|
46628
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
46070
46629
|
}
|
|
46071
46630
|
function isAlreadyExistsError(message) {
|
|
46072
46631
|
return /already\s+exists?\b/i.test(message) || /duplicate/i.test(message);
|
|
@@ -46199,7 +46758,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
46199
46758
|
const locId = client.resolveLocationId(locationId2);
|
|
46200
46759
|
const safePath = validateTemplatePath(templateFile);
|
|
46201
46760
|
const template = TemplateSchema.parse(JSON.parse(fs36.readFileSync(safePath, "utf-8")));
|
|
46202
|
-
const
|
|
46761
|
+
const resolve13 = (text2) => {
|
|
46203
46762
|
if (typeof text2 !== "string") return text2;
|
|
46204
46763
|
let result = text2;
|
|
46205
46764
|
for (const [key, value] of Object.entries(answers)) {
|
|
@@ -46212,7 +46771,7 @@ function registerTemplateDeployerTools(server2, client) {
|
|
|
46212
46771
|
return result;
|
|
46213
46772
|
};
|
|
46214
46773
|
const resolveObj = (obj) => {
|
|
46215
|
-
if (typeof obj === "string") return
|
|
46774
|
+
if (typeof obj === "string") return resolve13(obj);
|
|
46216
46775
|
if (Array.isArray(obj)) return obj.map(resolveObj);
|
|
46217
46776
|
if (obj && typeof obj === "object") {
|
|
46218
46777
|
const result = {};
|
|
@@ -47319,7 +47878,8 @@ function registerAgencyProfileTools(server2) {
|
|
|
47319
47878
|
defaults: import_zod74.z.object({
|
|
47320
47879
|
timezone: import_zod74.z.string().optional(),
|
|
47321
47880
|
industry: import_zod74.z.string().optional()
|
|
47322
|
-
}).optional()
|
|
47881
|
+
}).optional(),
|
|
47882
|
+
clientFilesDir: import_zod74.z.string().optional().describe("A full folder path on this machine where client call files are also written, usually your Google Drive for desktop folder so Drive syncs them. A relative path is refused.")
|
|
47323
47883
|
},
|
|
47324
47884
|
async (args) => {
|
|
47325
47885
|
const patch = Object.fromEntries(
|
|
@@ -47897,7 +48457,7 @@ function blockContrast(t, fallbackBg) {
|
|
|
47897
48457
|
if (!fg || !bg) return null;
|
|
47898
48458
|
return Math.round(contrastHex(fg, bg) * 100) / 100;
|
|
47899
48459
|
}
|
|
47900
|
-
function
|
|
48460
|
+
function inside2(inner, outer, inset = 0) {
|
|
47901
48461
|
return inner.x >= outer.x + inset - 0.5 && inner.y >= outer.y + inset - 0.5 && inner.x + inner.w <= outer.x + outer.w - inset + 0.5 && inner.y + inner.h <= outer.y + outer.h - inset + 0.5;
|
|
47902
48462
|
}
|
|
47903
48463
|
function clusterBoxes(boxes, gap = 16) {
|
|
@@ -47992,7 +48552,7 @@ function checkSlides(probe, pixels, ctx, rendered) {
|
|
|
47992
48552
|
const bad = [];
|
|
47993
48553
|
for (const s of slides) {
|
|
47994
48554
|
const inset = Math.round(s.box.w * 0.05);
|
|
47995
|
-
const outside = [...s.textBlocks.filter((t) => t.words > 0).map((t) => ({ box: t.box, what: `"${t.text.slice(0, 20)}"` })), ...s.images.map((i) => ({ box: i.box, what: i.tag }))].filter((e) => !
|
|
48555
|
+
const outside = [...s.textBlocks.filter((t) => t.words > 0).map((t) => ({ box: t.box, what: `"${t.text.slice(0, 20)}"` })), ...s.images.map((i) => ({ box: i.box, what: i.tag }))].filter((e) => !inside2(e.box, s.box, inset));
|
|
47996
48556
|
if (outside.length) bad.push(`${slideName(s)}: ${outside.slice(0, 2).map((o) => o.what).join(", ")} inside the ${inset} px band`);
|
|
47997
48557
|
}
|
|
47998
48558
|
out.push(graded(rule("S5"), bad.length === 0, bad.length ? fails3(bad) : `all content \u2265${Math.round(slides[0].box.w * 0.05)} px from every edge`));
|
|
@@ -48214,7 +48774,7 @@ function checkSlides(probe, pixels, ctx, rendered) {
|
|
|
48214
48774
|
const l = logos[0];
|
|
48215
48775
|
if (l) {
|
|
48216
48776
|
if (l.box.h > 28) bad.push(`${slideName(s)} logo ${Math.round(l.box.h)} px tall`);
|
|
48217
|
-
if (!
|
|
48777
|
+
if (!inside2(l.box, s.box, inset)) bad.push(`${slideName(s)} logo inside the safe band`);
|
|
48218
48778
|
} else if (byHook && ctx.logoSig) bad.push(`${slideName(s)} has a data-role="logo" element but not the stored logo's bytes (conformance)`);
|
|
48219
48779
|
}
|
|
48220
48780
|
out.push(graded(rule("S21"), bad.length === 0, bad.length ? fails3(bad) : "stored logo on every slide, \u226424 px, inside the safe zone", void 0, ctx.logoSig ? void 0 : 'No stored logo to match; only the data-role="logo" hook was available.'));
|
|
@@ -48774,20 +49334,20 @@ function checkConversion(p, ctx, rendered) {
|
|
|
48774
49334
|
invented.push(`quote attributed to ${t.name} that they did not give`);
|
|
48775
49335
|
}
|
|
48776
49336
|
}
|
|
48777
|
-
const
|
|
49337
|
+
const digits = (x) => x.replace(/[,\s]/g, "");
|
|
48778
49338
|
const STOP = /* @__PURE__ */ new Set(["the", "and", "for", "with", "from", "our", "over", "of", "in", "on", "a", "an", "we", "us", "have", "been"]);
|
|
48779
49339
|
const unitAfter = (text2, at) => norm4(text2.slice(at)).split(" ").slice(1).filter((w) => w && !STOP.has(w) && !/^[\d,.]+$/.test(w)).slice(0, 2);
|
|
48780
49340
|
const briefFigures = b.proof.aggregates.flatMap((agg) => {
|
|
48781
49341
|
const line3 = `${agg.value} ${agg.label}`;
|
|
48782
|
-
return [...line3.matchAll(/\d[\d,.]*/g)].map((m) => ({ n:
|
|
49342
|
+
return [...line3.matchAll(/\d[\d,.]*/g)].map((m) => ({ n: digits(m[0]), units: /* @__PURE__ */ new Set([...unitAfter(line3, m.index ?? 0), ...norm4(agg.label).split(" ").filter((w) => w && !STOP.has(w))]) }));
|
|
48783
49343
|
});
|
|
48784
49344
|
const seenClaims = /* @__PURE__ */ new Set();
|
|
48785
49345
|
const claimed = (text2) => {
|
|
48786
49346
|
for (const m of text2.matchAll(/\d[\d,.]*/g)) {
|
|
48787
|
-
const key = `${
|
|
49347
|
+
const key = `${digits(m[0])}@${norm4(text2).slice(0, 40)}`;
|
|
48788
49348
|
if (seenClaims.has(key)) continue;
|
|
48789
49349
|
seenClaims.add(key);
|
|
48790
|
-
const hit = briefFigures.find((f) => f.n ===
|
|
49350
|
+
const hit = briefFigures.find((f) => f.n === digits(m[0]));
|
|
48791
49351
|
if (!hit) {
|
|
48792
49352
|
invented.push(`figure "${m[0]}" in "${text2.slice(0, 30)}"`);
|
|
48793
49353
|
continue;
|
|
@@ -50216,14 +50776,14 @@ function registerDesignStudioTools(server2) {
|
|
|
50216
50776
|
d.provenance.evidence["shape.radius"] = `set by ${stamp}`;
|
|
50217
50777
|
}
|
|
50218
50778
|
if (a.logoSvg) {
|
|
50219
|
-
let
|
|
50779
|
+
let clean2;
|
|
50220
50780
|
try {
|
|
50221
|
-
|
|
50781
|
+
clean2 = sanitizeSvg(a.logoSvg);
|
|
50222
50782
|
} catch (e) {
|
|
50223
50783
|
throw new Error(`${e.message} Nothing was changed.`);
|
|
50224
50784
|
}
|
|
50225
|
-
a.logoSvg =
|
|
50226
|
-
d.logo = { inlineSvg:
|
|
50785
|
+
a.logoSvg = clean2;
|
|
50786
|
+
d.logo = { inlineSvg: clean2, whiteFillFixed: false, source: `set by ${stamp}`, rights: "declared" };
|
|
50227
50787
|
d.provenance.evidence["logo"] = `set by ${stamp}`;
|
|
50228
50788
|
}
|
|
50229
50789
|
if (a.tone) d.voice.tone = a.tone;
|
|
@@ -50735,7 +51295,7 @@ function registerConnectorTools(server2) {
|
|
|
50735
51295
|
"Store a connector's credentials on THIS machine so that system can be read. The key is written next to your GoHighLevel credentials, readable only by you, and is never sent anywhere, never returned by any tool, and never written to a log. Stripe answers who owes you money, what each person or company pays you and when they renew. Slack lets the morning brief and a stopped build land in the channel you already look at, and shows what is waiting unanswered in the channels the bot is in. Your own Gmail, Google Calendar and Drive need no key here: the brief asks the connectors your assistant already has.",
|
|
50736
51296
|
{
|
|
50737
51297
|
system: import_zod77.z.enum(["stripe", "slack"]).describe("The system to connect: stripe or slack."),
|
|
50738
|
-
key: import_zod77.z.string().min(8).describe("Stripe: a RESTRICTED key with read access to subscriptions, invoices and customers. Slack: a bot token (starts with xoxb-) from a Slack app you own, with chat:write to post and channels:read plus channels:history to read what is waiting. Nothing here needs a key that can do more."),
|
|
51298
|
+
key: import_zod77.z.string().min(8).max(4096).describe("Stripe: a RESTRICTED key with read access to subscriptions, invoices and customers. Slack: a bot token (starts with xoxb-) from a Slack app you own, with chat:write to post and channels:read plus channels:history to read what is waiting. Nothing here needs a key that can do more."),
|
|
50739
51299
|
label: import_zod77.z.string().optional().describe("Which account this is, so you can tell two apart later.")
|
|
50740
51300
|
},
|
|
50741
51301
|
connectSystem
|
|
@@ -51451,7 +52011,7 @@ function planFingerprint(goal, steps) {
|
|
|
51451
52011
|
goal,
|
|
51452
52012
|
steps: steps.map((s) => ({ id: s.id, tool: s.tool, targets: s.targets, creates: s.creates ?? [], args: s.args ?? {} }))
|
|
51453
52013
|
});
|
|
51454
|
-
return (0,
|
|
52014
|
+
return (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 16);
|
|
51455
52015
|
}
|
|
51456
52016
|
function checkPlan(goal, findings, steps, now = /* @__PURE__ */ new Date(), knownTools) {
|
|
51457
52017
|
const refusals = [];
|
|
@@ -51547,7 +52107,7 @@ function position(a) {
|
|
|
51547
52107
|
const cyclic = a.steps.length > 0 && check.order.length === 0;
|
|
51548
52108
|
const order = check.order.length ? check.order : a.steps.map((s) => s.id);
|
|
51549
52109
|
const lastAttempt = /* @__PURE__ */ new Map();
|
|
51550
|
-
for (const x of [...a.applied].sort((p,
|
|
52110
|
+
for (const x of [...a.applied].sort((p, q3) => p.at < q3.at ? -1 : p.at > q3.at ? 1 : 0)) {
|
|
51551
52111
|
lastAttempt.set(x.stepId, x);
|
|
51552
52112
|
}
|
|
51553
52113
|
const doneIds = new Set([...lastAttempt.values()].filter((x) => x.state === "applied").map((x) => x.stepId));
|
|
@@ -51590,7 +52150,7 @@ function position(a) {
|
|
|
51590
52150
|
line3 = `STALLED before it started: this plan's steps wait on each other (${names}), so there is no order that works. It cannot be run as it stands and needs planning again.`;
|
|
51591
52151
|
} else if (failed.length) {
|
|
51592
52152
|
phase = "stalled";
|
|
51593
|
-
const f = failed.reduce((p,
|
|
52153
|
+
const f = failed.reduce((p, q3) => q3.at > p.at ? q3 : p);
|
|
51594
52154
|
stalledOn = { step: f.stepId, because: f.because ?? "no reason was recorded" };
|
|
51595
52155
|
const rest = total - done - 1;
|
|
51596
52156
|
line3 = `STALLED on ${f.stepId} (${byId2.get(f.stepId)?.why ?? "step"}): ${f.because ?? "no reason was recorded"}. ${done} of ${total} steps are done and the remaining ${rest === 1 ? "step is" : `${rest} steps are`} on hold \u2014 nothing else runs until this one is sorted, even the steps that do not depend on it, because the plan's order is part of what was approved. Fix what went wrong and record ${f.stepId} again with retry, or abandon the build.`;
|
|
@@ -51668,11 +52228,11 @@ function abandon(a, why) {
|
|
|
51668
52228
|
const stillApplied = position(a).undo;
|
|
51669
52229
|
return { ok: true, automation: { ...a, phase: "abandoned", goal: a.goal, abandonedBecause: why }, stillApplied };
|
|
51670
52230
|
}
|
|
51671
|
-
var
|
|
52231
|
+
var import_node_crypto12, isExisting, FINDINGS_STALE_AFTER_MINUTES;
|
|
51672
52232
|
var init_automation = __esm({
|
|
51673
52233
|
"src/command-os/build/automation.ts"() {
|
|
51674
52234
|
"use strict";
|
|
51675
|
-
|
|
52235
|
+
import_node_crypto12 = require("node:crypto");
|
|
51676
52236
|
init_verify_findings();
|
|
51677
52237
|
isExisting = (t) => "existing" in t;
|
|
51678
52238
|
FINDINGS_STALE_AFTER_MINUTES = 60;
|
|
@@ -52295,15 +52855,15 @@ var init_agency_build = __esm({
|
|
|
52295
52855
|
|
|
52296
52856
|
// src/tools/client-engagements.ts
|
|
52297
52857
|
function resolveClient(who, roster) {
|
|
52298
|
-
const
|
|
52299
|
-
if (!
|
|
52300
|
-
const byId2 = roster.find((r) => r.locationId ===
|
|
52858
|
+
const q3 = who.trim();
|
|
52859
|
+
if (!q3) return { error: "Name who you mean \u2014 either the way you refer to them, or their account id." };
|
|
52860
|
+
const byId2 = roster.find((r) => r.locationId === q3);
|
|
52301
52861
|
if (byId2) return { locationId: byId2.locationId, name: byId2.name, onTheBoard: true };
|
|
52302
|
-
const hits = roster.filter((r) => r.name?.trim().toLowerCase() ===
|
|
52303
|
-
const loose2 = hits.length ? hits : roster.filter((r) => r.name?.toLowerCase().includes(
|
|
52862
|
+
const hits = roster.filter((r) => r.name?.trim().toLowerCase() === q3.toLowerCase());
|
|
52863
|
+
const loose2 = hits.length ? hits : roster.filter((r) => r.name?.toLowerCase().includes(q3.toLowerCase()));
|
|
52304
52864
|
if (loose2.length === 1) return { locationId: loose2[0].locationId, name: loose2[0].name, onTheBoard: true };
|
|
52305
|
-
if (loose2.length > 1) return { error: `More than one match for "${
|
|
52306
|
-
return { error: `Nothing called "${
|
|
52865
|
+
if (loose2.length > 1) return { error: `More than one match for "${q3}": ${loose2.map((r) => r.name).join(", ")}. Use the full name.` };
|
|
52866
|
+
return { error: `Nothing called "${q3}" is on your board or in your records.` };
|
|
52307
52867
|
}
|
|
52308
52868
|
function line2(locationId2, e, roster, profile) {
|
|
52309
52869
|
const boardName = roster.find((r) => r.locationId === locationId2)?.name;
|
|
@@ -52558,9 +53118,9 @@ function looksLikeRawId(word) {
|
|
|
52558
53118
|
if (/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(word)) return true;
|
|
52559
53119
|
if (/^[0-9a-f]{24}$/.test(word)) return true;
|
|
52560
53120
|
if (!/^[A-Za-z0-9]{17,}$/.test(word)) return false;
|
|
52561
|
-
const
|
|
53121
|
+
const digits = (word.match(/\d/g) ?? []).length;
|
|
52562
53122
|
const letters = (word.match(/[A-Za-z]/g) ?? []).length;
|
|
52563
|
-
if (
|
|
53123
|
+
if (digits < 2 || letters < 2) return false;
|
|
52564
53124
|
const longestLetterRun = Math.max(0, ...(word.match(/[a-z]+|[A-Z]+/g) ?? []).map((r) => r.length));
|
|
52565
53125
|
return longestLetterRun <= 3;
|
|
52566
53126
|
}
|
|
@@ -53607,7 +54167,7 @@ function planRevert(record, opts) {
|
|
|
53607
54167
|
function errText(e) {
|
|
53608
54168
|
return e instanceof Error ? e.message : String(e);
|
|
53609
54169
|
}
|
|
53610
|
-
async function executeRevert(plan, deps, wait = (ms) => new Promise((
|
|
54170
|
+
async function executeRevert(plan, deps, wait = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms))) {
|
|
53611
54171
|
const removed = [];
|
|
53612
54172
|
const refused = [];
|
|
53613
54173
|
const attempted = [];
|
|
@@ -53726,8 +54286,8 @@ function renderDryRun(plan, opts = {}) {
|
|
|
53726
54286
|
const children2 = withParent.filter((w) => w.object.parentRef && g.items.some((i) => i.object.ref === w.object.parentRef));
|
|
53727
54287
|
if (children2.length) {
|
|
53728
54288
|
const kind = children2[0].object.type;
|
|
53729
|
-
const
|
|
53730
|
-
L.push(` (plus the ${children2.length} ${PLURAL[kind].toLowerCase()} ${
|
|
54289
|
+
const inside3 = g.items.length === 1 ? "inside it" : "inside them";
|
|
54290
|
+
L.push(` (plus the ${children2.length} ${PLURAL[kind].toLowerCase()} ${inside3}, which GoHighLevel removes at the same time)`);
|
|
53731
54291
|
}
|
|
53732
54292
|
}
|
|
53733
54293
|
const kept = [...bound, ...cannot, ...gone];
|
|
@@ -54920,11 +55480,11 @@ function extractFormFields(formFull) {
|
|
|
54920
55480
|
const fields = Array.isArray(inner.fields) ? inner.fields : [];
|
|
54921
55481
|
return fields.filter((f) => !!f && typeof f === "object");
|
|
54922
55482
|
}
|
|
54923
|
-
function findRecordForQuestion(
|
|
54924
|
-
const wantKey = expectedFieldKey(
|
|
55483
|
+
function findRecordForQuestion(q3, records) {
|
|
55484
|
+
const wantKey = expectedFieldKey(q3).toLowerCase();
|
|
54925
55485
|
const byKey = records.find((r) => r.fieldKey.toLowerCase() === wantKey);
|
|
54926
55486
|
if (byKey) return byKey;
|
|
54927
|
-
const wantName = intakeFieldName(
|
|
55487
|
+
const wantName = intakeFieldName(q3.label).toLowerCase();
|
|
54928
55488
|
return records.find((r) => r.name.toLowerCase() === wantName);
|
|
54929
55489
|
}
|
|
54930
55490
|
function isFormNotYetPropagated(error) {
|
|
@@ -55549,17 +56109,17 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
55549
56109
|
industry: industryPack(industry)?.slug,
|
|
55550
56110
|
intake: describeComposition({ industry, agency }),
|
|
55551
56111
|
count: questions.length,
|
|
55552
|
-
questions: questions.map((
|
|
55553
|
-
key:
|
|
55554
|
-
label:
|
|
55555
|
-
section:
|
|
55556
|
-
required:
|
|
55557
|
-
fieldKind:
|
|
55558
|
-
dataType:
|
|
55559
|
-
standardTag:
|
|
55560
|
-
expectedFieldKey:
|
|
55561
|
-
options:
|
|
55562
|
-
briefPath:
|
|
56112
|
+
questions: questions.map((q3) => ({
|
|
56113
|
+
key: q3.key,
|
|
56114
|
+
label: q3.label,
|
|
56115
|
+
section: q3.sectionLabel,
|
|
56116
|
+
required: q3.required,
|
|
56117
|
+
fieldKind: q3.field.kind,
|
|
56118
|
+
dataType: q3.field.kind === "custom" ? q3.field.dataType : void 0,
|
|
56119
|
+
standardTag: q3.field.kind === "standard" ? q3.field.tag : void 0,
|
|
56120
|
+
expectedFieldKey: q3.field.kind === "custom" ? expectedFieldKey(q3) : void 0,
|
|
56121
|
+
options: q3.options,
|
|
56122
|
+
briefPath: q3.briefPath
|
|
55563
56123
|
}))
|
|
55564
56124
|
};
|
|
55565
56125
|
}
|
|
@@ -56163,26 +56723,26 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
56163
56723
|
if (!builderClient) return;
|
|
56164
56724
|
const bc = builderClient;
|
|
56165
56725
|
async function resolveCustomFields(locationId2, dryRun, questions) {
|
|
56166
|
-
const cqs = questions.filter((
|
|
56726
|
+
const cqs = questions.filter((q3) => q3.field.kind === "custom");
|
|
56167
56727
|
const existing = parseCustomFields(await client.get(`/locations/${locationId2}/customFields`));
|
|
56168
56728
|
const created = [];
|
|
56169
56729
|
const reused = [];
|
|
56170
|
-
for (const
|
|
56171
|
-
const found = findRecordForQuestion(
|
|
56730
|
+
for (const q3 of cqs) {
|
|
56731
|
+
const found = findRecordForQuestion(q3, existing);
|
|
56172
56732
|
if (found) {
|
|
56173
|
-
reused.push(
|
|
56733
|
+
reused.push(q3.label);
|
|
56174
56734
|
} else {
|
|
56175
|
-
created.push(
|
|
56735
|
+
created.push(q3.label);
|
|
56176
56736
|
if (!dryRun) {
|
|
56177
56737
|
const body = {
|
|
56178
|
-
name: intakeFieldName(
|
|
56179
|
-
dataType:
|
|
56738
|
+
name: intakeFieldName(q3.label),
|
|
56739
|
+
dataType: q3.field.kind === "custom" ? q3.field.dataType : "TEXT",
|
|
56180
56740
|
model: "contact"
|
|
56181
56741
|
};
|
|
56182
|
-
if (
|
|
56183
|
-
const dt =
|
|
56742
|
+
if (q3.options && q3.field.kind === "custom") {
|
|
56743
|
+
const dt = q3.field.dataType;
|
|
56184
56744
|
if (dt === "SINGLE_OPTIONS" || dt === "MULTIPLE_OPTIONS" || dt === "CHECKBOX") {
|
|
56185
|
-
body.options = [...
|
|
56745
|
+
body.options = [...q3.options];
|
|
56186
56746
|
}
|
|
56187
56747
|
}
|
|
56188
56748
|
await client.post(`/locations/${locationId2}/customFields`, { body });
|
|
@@ -56196,13 +56756,13 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
56196
56756
|
const after = parseCustomFields(await client.get(`/locations/${locationId2}/customFields`));
|
|
56197
56757
|
resolved2.clear();
|
|
56198
56758
|
missing = void 0;
|
|
56199
|
-
for (const
|
|
56200
|
-
const rec = findRecordForQuestion(
|
|
56759
|
+
for (const q3 of cqs) {
|
|
56760
|
+
const rec = findRecordForQuestion(q3, after);
|
|
56201
56761
|
if (!rec) {
|
|
56202
|
-
missing =
|
|
56762
|
+
missing = q3;
|
|
56203
56763
|
break;
|
|
56204
56764
|
}
|
|
56205
|
-
resolved2.set(
|
|
56765
|
+
resolved2.set(q3.key, rec);
|
|
56206
56766
|
}
|
|
56207
56767
|
if (!missing) break;
|
|
56208
56768
|
if (attempt < 6) await sleep6(700 * attempt);
|
|
@@ -56252,7 +56812,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
|
|
|
56252
56812
|
industry: pack?.slug,
|
|
56253
56813
|
intake,
|
|
56254
56814
|
questionCount: questions.length,
|
|
56255
|
-
standardFields: questions.filter((
|
|
56815
|
+
standardFields: questions.filter((q3) => q3.field.kind === "standard").map((q3) => q3.label),
|
|
56256
56816
|
customFieldsToCreate: created,
|
|
56257
56817
|
customFieldsToReuse: reused,
|
|
56258
56818
|
note: "No changes written. Re-run without dryRun to install."
|
|
@@ -56989,12 +57549,12 @@ init_setup_tool();
|
|
|
56989
57549
|
var import_zod87 = require("zod");
|
|
56990
57550
|
|
|
56991
57551
|
// src/skill-installer.ts
|
|
56992
|
-
var
|
|
57552
|
+
var import_node_crypto13 = require("node:crypto");
|
|
56993
57553
|
var fs42 = __toESM(require("node:fs"));
|
|
56994
57554
|
var path40 = __toESM(require("node:path"));
|
|
56995
57555
|
var os7 = __toESM(require("node:os"));
|
|
56996
57556
|
function sha2562(buf) {
|
|
56997
|
-
return (0,
|
|
57557
|
+
return (0, import_node_crypto13.createHash)("sha256").update(buf).digest("hex");
|
|
56998
57558
|
}
|
|
56999
57559
|
function isOlderRelease(candidate, installed) {
|
|
57000
57560
|
const parts = (v) => v.trim().split(".").map((n) => Number.parseInt(n, 10));
|