@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/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((resolve12) => setTimeout(resolve12, ms));
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: q2, startAt, endAt }) => {
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 (q2 !== void 0) params.q = q2;
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: q2, startAt, endAt }) => {
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 (q2 !== void 0) params.q = q2;
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 digits2 = "23456789";
6195
+ const digits = "23456789";
6198
6196
  const symbols = "!@#$%&*?";
6199
- const all = upper2 + lower + digits2 + symbols;
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(digits2, bytes[2]), pick2(symbols, bytes[3])];
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 clean = {};
8336
- for (const [k, val] of Object.entries(change)) if (val !== void 0) clean[k] = val;
8337
- if (Object.keys(clean).length === 0) throw new Error("Nothing to update \u2014 pass at least one field to change.");
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), ...clean };
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(clean), after };
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 stop = run.endedAt ? Date.parse(run.endedAt) || Infinity : Infinity;
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 > stop + SKEW_MS) {
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, q2 = {}) {
11620
- if (q2.categories?.length) {
11621
- const wanted = new Set(q2.categories.map((name) => name.trim().toLowerCase()));
11622
- const limit = Math.min(Math.max(q2.limit ?? 24, 1), 200);
11623
- const offset = Math.max(q2.offset ?? 0, 0);
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, { ...q2, categories: [], limit: 200, offset: upstreamOffset });
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: q2.product ?? "funnels",
11643
- keyword: q2.keyword ?? "",
11640
+ product: q3.product ?? "funnels",
11641
+ keyword: q3.keyword ?? "",
11644
11642
  type: "all",
11645
- categories: q2.categories ?? [],
11646
- features: q2.features ?? [],
11647
- tags: q2.tags ?? [],
11648
- colors: q2.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(q2.limit ?? 24, 1), 200),
11654
- offset: q2.offset ?? 0
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 q2 = afterByPath.get(p.path);
12026
- const to = q2?.colour ?? null;
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 clean = sanitizeSvgOrDrop(svg);
15706
- if (clean) {
15707
- logoSvg = clean;
15708
- logo = { inlineSvg: clean, whiteFillFixed: fixed, source: bl.where, rights: opts.rights ? "declared" : "inherits", ...vb && { width: parseFloat(vb[1]), height: parseFloat(vb[2]) } };
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" : ""}${clean !== svg ? "; scripts, handlers and outside references stripped" : ""}`;
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 normalize = (s) => s.replace(/^\/+|\/+$/g, "").toLowerCase();
16658
- const base = normalize(path42 ?? "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "page";
16659
- const used = new Set([...taken].map(normalize));
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 normalize = (s) => s.replace(/^\/+|\/+$/g, "");
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 && normalize(d2.path) === normalize(path42));
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 : normalize(d.newPath)}${suffix}`;
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((resolve12) => setTimeout(resolve12, attempt * 1e3));
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 same = (key, a, b) => key === "phone" ? (a ?? "").replace(/\D/g, "") === (b ?? "").replace(/\D/g, "") : (a ?? "").trim().toLowerCase() === (b ?? "").trim().toLowerCase();
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 (!same(key, got[key], exp)) probs.push(`standard ${k}: saved "${got[key]}" \u2260 sent "${exp}"`);
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-answers.ts
20010
- function readBriefAnswers() {
20011
- try {
20012
- return FileSchema.parse(JSON.parse(fs11.readFileSync(briefAnswersPath(), "utf8")));
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 path11.join(appDataDir(), "connectors.json");
20160
+ return path10.join(appDataDir(), "connectors.json");
20169
20161
  }
20170
20162
  function readFile() {
20171
20163
  try {
20172
- const raw = fs12.readFileSync(connectorsPath(), "utf-8");
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 = fs12.readFileSync(target, "utf-8");
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
- fs12.copyFileSync(target, kept);
20197
- fs12.chmodSync(kept, 384);
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
- fs12.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
20203
+ fs11.writeFileSync(tmp, JSON.stringify(f, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
20212
20204
  try {
20213
- fs12.chmodSync(tmp, 384);
20205
+ fs11.chmodSync(tmp, 384);
20214
20206
  } catch {
20215
20207
  }
20216
- fs12.renameSync(tmp, target);
20208
+ fs11.renameSync(tmp, target);
20217
20209
  renamed = true;
20218
20210
  } finally {
20219
20211
  if (!renamed) {
20220
20212
  try {
20221
- fs12.unlinkSync(tmp);
20213
+ fs11.unlinkSync(tmp);
20222
20214
  } catch {
20223
20215
  }
20224
20216
  }
20225
20217
  }
20226
20218
  try {
20227
- fs12.chmodSync(target, 384);
20219
+ fs11.chmodSync(target, 384);
20228
20220
  } catch {
20229
20221
  }
20230
20222
  }
20231
20223
  function safeLabel(label, key) {
20232
- const variants = [.../* @__PURE__ */ new Set([key, key.trim()])].filter(Boolean);
20233
- if (label === void 0 || !variants.length) return label;
20234
- const pattern = variants.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
20235
- return label.replace(new RegExp(pattern, "g"), "[key removed from label]");
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(fs12.readFileSync(connectorsPath(), "utf8")));
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 fs12, import_zod50, path11, hintOf, SystemSchema, SummarySourceSchema;
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
- fs12 = __toESM(require("node:fs"));
20285
+ fs11 = __toESM(require("node:fs"));
20289
20286
  import_zod50 = require("zod");
20290
- path11 = __toESM(require("node:path"));
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", "meta-ads", "byo"]);
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 q2 = i.quantity;
20528
- const qty = typeof q2 === "number" && isFinite(q2) && q2 > 0 ? Math.round(q2) : 1;
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(fs13.readFileSync(googleCalendarPath(), "utf8")));
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") fs13.chmodSync(dir, 448);
20791
- tmp = `${googleCalendarPath()}.${(0, import_node_crypto5.randomUUID)()}.tmp`;
20792
- fs13.writeFileSync(tmp, JSON.stringify(validated) + "\n", { mode: 384, flag: "wx" });
20793
- fs13.renameSync(tmp, googleCalendarPath());
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
- fs13.unlinkSync(tmp);
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
- fs13.unlinkSync(googleCalendarPath());
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 fs13, path12, import_node_crypto5, import_zod51, CALENDAR_SCOPE, SecretSchema, googleCalendarPath;
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
- fs13 = __toESM(require("node:fs"));
20819
- path12 = __toESM(require("node:path"));
20820
- import_node_crypto5 = require("node:crypto");
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 = () => path12.join(appDataDir(), "google-calendar.json");
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((resolve12) => (0, import_node_child_process.execFile)(file2, args, { timeout: 5e3 }, (error) => resolve12(!error)));
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, import_node_crypto6.randomBytes)(32).toString("base64url");
20981
- const verifier = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
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((resolve12, reject) => {
21011
+ await new Promise((resolve13, reject) => {
21015
21012
  server2.once("error", reject);
21016
- server2.listen(0, "127.0.0.1", resolve12);
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, import_node_crypto6.createHash)("sha256").update(verifier).digest("base64url"),
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, import_node_crypto6, import_node_child_process, import_zod52, TOKEN_URL, REVOKE_URL, API, tokenSchema, calendarSchema, eventSchema;
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
- import_node_crypto6 = require("node:crypto");
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 : path13.join(appDataDir(), "client-engagements.json");
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 clean = sanitizeEngagement(rec);
21438
- if (clean) clients[key] = clean;
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(fs14.readFileSync(file2, "utf8"));
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 fs14.readFileSync(file2, "utf8");
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
- fs14.mkdirSync(path13.dirname(file2), { recursive: true });
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
- fs14.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
21492
+ fs13.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
21496
21493
  if (process.platform !== "win32") {
21497
21494
  try {
21498
- fs14.chmodSync(tmp, 384);
21495
+ fs13.chmodSync(tmp, 384);
21499
21496
  } catch {
21500
21497
  }
21501
21498
  }
21502
- fs14.renameSync(tmp, file2);
21499
+ fs13.renameSync(tmp, file2);
21503
21500
  } catch (e) {
21504
21501
  try {
21505
- fs14.unlinkSync(tmp);
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 fs14, path13, CLIENT_ENGAGEMENTS_ENV, ENGAGEMENT_STATUSES, ENGAGEMENT_KINDS, DEFAULT_ENGAGEMENT_KIND, EMPTY2, MAX_NOTES, MAX_SOP2, MAX_CLIENTS, FORBIDDEN_KEY;
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
- fs14 = __toESM(require("fs"));
21582
- path13 = __toESM(require("path"));
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(fs15.readFileSync(path14.join(appDataDir(), "cockpit-state.json"), "utf8"));
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 fs15, path14;
21615
+ var fs14, path13;
21619
21616
  var init_board_roster = __esm({
21620
21617
  "src/board-roster.ts"() {
21621
21618
  "use strict";
21622
- fs15 = __toESM(require("fs"));
21623
- path14 = __toESM(require("path"));
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
- path15.join(__dirname, "..", "skills", "blueprint"),
21835
+ path14.join(__dirname, "..", "skills", "blueprint"),
21839
21836
  // dist/ → package root
21840
- path15.join(__dirname, "..", "..", "skills", "blueprint"),
21837
+ path14.join(__dirname, "..", "..", "skills", "blueprint"),
21841
21838
  // src/ in dev
21842
- path15.join(process.cwd(), "skills", "blueprint")
21839
+ path14.join(process.cwd(), "skills", "blueprint")
21843
21840
  ];
21844
- return candidates.find((c) => fs16.existsSync(path15.join(c, "references", "build-plan-schema.md"))) ?? null;
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 = fs16.readFileSync(p, "utf8");
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(path15.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
21858
+ const schema = readIf(path14.join(root, "references", "build-plan-schema.md"), CAP_SCHEMA);
21862
21859
  const presetName = presetForIndustry(industry);
21863
- const preset = readIf(path15.join(root, "presets", `${presetName}.preset.json`), CAP_PRESET);
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(path15.join(root, "examples", exampleFile), CAP_EXAMPLE);
21866
- const copyGuide = readIf(path15.join(root, "references", "copy-guide.md"), CAP_COPY_GUIDE);
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 fs16, path15, PRESET_FOR_INDUSTRY, EXAMPLE_FOR_PRESET, COPY_INSTRUCTION, CALENDAR_INSTRUCTION, CAP_SCHEMA, CAP_PRESET, CAP_EXAMPLE, CAP_COPY_GUIDE;
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
- fs16 = __toESM(require("fs"));
21889
- path15 = __toESM(require("path"));
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(q2) {
21920
- return deriveFieldKey(intakeFieldName(q2.label));
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 q2 of questions) {
21926
- if (q2.section !== last) {
21922
+ for (const q3 of questions) {
21923
+ if (q3.section !== last) {
21927
21924
  count2++;
21928
- last = q2.section;
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 path16.join(base, "progress");
24506
+ return path15.join(base, "progress");
24510
24507
  }
24511
24508
  function progressFilePath(dir, locationId2) {
24512
- return path16.join(dir, `${locationId2.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
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
- fs17.mkdirSync(dir, { recursive: true, mode: 448 });
24517
+ fs16.mkdirSync(dir, { recursive: true, mode: 448 });
24521
24518
  const tmp = `${file2}.tmp`;
24522
- fs17.writeFileSync(tmp, JSON.stringify({ locationId: locationId2, progress: tracker.snapshot() }), { mode: 384 });
24523
- fs17.renameSync(tmp, file2);
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(fs17.readFileSync(progressFilePath(dir, locationId2), "utf8"));
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 fs17, path16, PROGRESS_GROUPS, GROUP_INDEX, TYPE_TO_GROUP, HALT_TYPE_TO_GROUP, CREATING_LINE, BuildProgressTracker, PROGRESS_DIR_ENV;
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
- fs17 = __toESM(require("fs"));
24575
- path16 = __toESM(require("path"));
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 path17.join(base, "build-journal");
24710
+ return path16.join(base, "build-journal");
24714
24711
  }
24715
24712
  function journalPath(locationId2, base) {
24716
- return path17.join(journalDir(base), `${locationId2}.jsonl`);
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
- fs18.mkdirSync(dir, { recursive: true, mode: 448 });
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 = fs18.statSync(file2);
24791
+ const st = fs17.statSync(file2);
24795
24792
  if (st.size > 0) {
24796
- const fd2 = fs18.openSync(file2, "r");
24793
+ const fd2 = fs17.openSync(file2, "r");
24797
24794
  try {
24798
24795
  const buf = Buffer.alloc(1);
24799
- fs18.readSync(fd2, buf, 0, 1, st.size - 1);
24796
+ fs17.readSync(fd2, buf, 0, 1, st.size - 1);
24800
24797
  needsBoundary = buf[0] !== 10;
24801
24798
  } finally {
24802
- fs18.closeSync(fd2);
24799
+ fs17.closeSync(fd2);
24803
24800
  }
24804
24801
  }
24805
24802
  } catch {
24806
24803
  }
24807
- const fd = fs18.openSync(file2, "a", 384);
24804
+ const fd = fs17.openSync(file2, "a", 384);
24808
24805
  try {
24809
- fs18.appendFileSync(fd, `${needsBoundary ? "\n" : ""}${JSON.stringify(record)}
24806
+ fs17.appendFileSync(fd, `${needsBoundary ? "\n" : ""}${JSON.stringify(record)}
24810
24807
  `);
24811
- fs18.fsyncSync(fd);
24808
+ fs17.fsyncSync(fd);
24812
24809
  } finally {
24813
- fs18.closeSync(fd);
24810
+ fs17.closeSync(fd);
24814
24811
  }
24815
- for (const directory of [dir, path17.dirname(dir)]) {
24812
+ for (const directory of [dir, path16.dirname(dir)]) {
24816
24813
  let dirFd;
24817
24814
  try {
24818
- dirFd = fs18.openSync(directory, "r");
24819
- fs18.fsyncSync(dirFd);
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) fs18.closeSync(dirFd);
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 = fs18.readFileSync(journalPath(locationId2, base), "utf8");
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 fs18, path17, DIRECTORY_FLUSH_UNSUPPORTED;
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
- fs18 = __toESM(require("fs"));
24907
- path17 = __toESM(require("path"));
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
- fs19.accessSync(p, fs19.constants.X_OK);
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 = path18.extname(file2).toLowerCase();
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 = fs19.readFileSync(shim, "utf8");
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 resolve12 = (rel) => path18.join(path18.dirname(shim), ...rel.split(/[\\/]/));
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 = resolve12(script);
24946
- return fs19.existsSync(p) ? { kind: "script", path: p } : null;
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 = resolve12(program);
24951
- return fs19.existsSync(p) ? { kind: "program", path: p } : null;
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 = path18.join(dir, name);
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 = path18.join(home, ".local", "bin", platform2 === "win32" ? "claude.exe" : "claude");
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 fs19, os3, path18, NOT_FOUND_MESSAGE;
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
- fs19 = __toESM(require("node:fs"));
24994
+ fs18 = __toESM(require("node:fs"));
24998
24995
  os3 = __toESM(require("node:os"));
24999
- path18 = __toESM(require("node:path"));
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 path19.join(appDataDir(), "command-os-engine.json");
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(fs20.readFileSync(file2, "utf8"));
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
- fs20.mkdirSync(path19.dirname(file2), { recursive: true });
25047
- fs20.writeFileSync(file2, JSON.stringify(rec, null, 2), { mode: 384 });
25043
+ fs19.mkdirSync(path18.dirname(file2), { recursive: true });
25044
+ fs19.writeFileSync(file2, JSON.stringify(rec, null, 2), { mode: 384 });
25048
25045
  try {
25049
- fs20.chmodSync(file2, 384);
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((resolve12) => {
25069
+ return new Promise((resolve13) => {
25073
25070
  let settled = false;
25074
25071
  const done = (r) => {
25075
25072
  if (!settled) {
25076
25073
  settled = true;
25077
- resolve12(r);
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, fs20, path19, MIN_CLAUDE_VERSION, SMOKE_PROMPT;
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
- fs20 = __toESM(require("fs"));
25194
- path19 = __toESM(require("path"));
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((resolve12) => {
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
- resolve12({ ok: false, error: engineCheck.reason, engineCheckCostUsd: engineCheck.smokeCostUsd, engineCheckRan: engineCheck.smoked, engineVersion: engineCheck.version, engineModel: engineCheck.smokeModel });
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
- fs21.rmSync(progressFilePath(progressDir, locationId2), { force: true });
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
- resolve12(outcome2);
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
- resolve12(outcome);
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
- resolve12(outcome);
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
- resolve12({ ok: false, error: line3 });
25422
+ resolve13({ ok: false, error: line3 });
25426
25423
  });
25427
25424
  });
25428
25425
  }
25429
- var import_child_process3, fs21, STAFF_NOTIFICATION_RULE, MISSING_STEP_RULE, NO_INVENTED_CAUSE_RULE, STAGE_SPECS, HUMAN_GATE_STAGES;
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
- fs21 = __toESM(require("fs"));
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 inside2 = opens.filter((t) => t.index > start && t.index < end);
26286
- headlinesPerSection.push(inside2.filter((t) => t.attrs["data-role"] === "headline").length);
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((resolve12) => {
26621
+ return new Promise((resolve13) => {
26625
26622
  let bin;
26626
26623
  try {
26627
26624
  bin = claudeBin();
26628
26625
  } catch {
26629
26626
  opts.onCost?.();
26630
- resolve12("");
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
- resolve12(lastText);
26665
+ resolve13(lastText);
26669
26666
  });
26670
26667
  child.on("close", () => {
26671
26668
  clearTimeout(timeout);
26672
26669
  report2();
26673
- resolve12(lastText);
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 q2 = payingButQuiet[0];
27767
- headline3 = payingButQuiet.length === 1 ? `${q2.client} is paying you and has booked nothing in ${q2.days} days.` : `${payingButQuiet.length} clients are paying you and have booked nothing in ${q2.days} days. The largest is ${q2.client}.`;
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((resolve12) => setTimeout(resolve12, ms));
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(fs22.readFileSync(timesPath(), "utf8")));
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, import_node_crypto7.randomUUID)()}.tmp`;
28561
+ const tmp = `${timesPath()}.${(0, import_node_crypto6.randomUUID)()}.tmp`;
28584
28562
  try {
28585
- fs22.writeFileSync(tmp, JSON.stringify(times) + "\n", { mode: 384, flag: "wx" });
28586
- fs22.renameSync(tmp, timesPath());
28563
+ fs21.writeFileSync(tmp, JSON.stringify(times) + "\n", { mode: 384, flag: "wx" });
28564
+ fs21.renameSync(tmp, timesPath());
28587
28565
  } finally {
28588
28566
  try {
28589
- fs22.unlinkSync(tmp);
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 fs22, path20, import_node_crypto7, import_zod57, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, amountText, stripePanel, slackPanel, calendarPanel;
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
- fs22 = __toESM(require("node:fs"));
28661
- path20 = __toESM(require("node:path"));
28662
- import_node_crypto7 = require("node:crypto");
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
- init_brief_answers();
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 = () => path20.join(appDataDir(), "brief-read-times.json");
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.6",
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 fs23, path21, bundledPkg, pkg;
29160
+ var fs22, path20, bundledPkg, pkg;
29202
29161
  var init_package_version = __esm({
29203
29162
  "src/package-version.ts"() {
29204
29163
  "use strict";
29205
- fs23 = __toESM(require("fs"));
29206
- path21 = __toESM(require("path"));
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
- fs23.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf8")
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 plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
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 ${plural(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.`
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(`${plural(live, "thing is", "things are")} live in your account`);
31483
- if (notYet) parts.push(`${plural(notYet, "is", "are")} built but not confirmed yet`);
31484
- if (waiting) parts.push(`${plural(waiting, "is", "are")} waiting on someone else`);
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 ? `${plural(notYet, "item", "items")} still to confirm` : `${plural(waiting, "item is", "items are")} waiting on someone else`,
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 path22.join(base, "client-status.json");
31759
+ return path21.join(base, "client-status.json");
31801
31760
  }
31802
31761
  function readStatusStore(base) {
31803
31762
  try {
31804
- const raw = JSON.parse(fs24.readFileSync(statusStorePath(base), "utf8"));
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
- fs24.mkdirSync(path22.dirname(file2), { recursive: true, mode: 448 });
31813
- fs24.writeFileSync(file2, JSON.stringify(store, null, 2), { mode: 384 });
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: ${path22.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.`;
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 ${path22.join(placement.directory, placement.fileName ?? "status.html")} for you to publish as an Artifact from your own Claude${placement.publicUrl ? `, currently at ${placement.publicUrl}` : ""}.`;
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 fs24, path22, STATUS_SLUG, OUR_HOSTS;
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
- fs24 = __toESM(require("node:fs"));
31911
- path22 = __toESM(require("node:path"));
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, import_node_crypto8.createHash)("sha256").update(JSON.stringify(canonical2(value)) ?? "null").digest("hex");
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 tokens = text2.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
31949
+ const tokens2 = text2.match(/(?:\[[^\]]*\]|[^\s[])+/g) ?? [];
31991
31950
  let end = "";
31992
- for (const token2 of tokens) {
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 resolve12 = (s) => s.replace(missingRefPattern, (ref) => names.get(ref) || `Unnamed ${kindOf2(ref.split(".")[0])}`);
32061
- const renderRefs = (s) => resolve12(refPattern ? s.replace(refPattern, (ref) => names.get(ref)) : 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 import_node_crypto8, import_node_fs3, import_node_path3, INTAKE_CHANGED;
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
- import_node_crypto8 = require("node:crypto");
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 path23.join(base, "handoffs");
32329
+ return path22.join(base, "handoffs");
32371
32330
  }
32372
32331
  function handoffSnapshotPath(locationId2, base) {
32373
- return path23.join(handoffSnapshotDir(base), `${locationId2}-handoff.json`);
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
- fs25.mkdirSync(path23.dirname(file2), { recursive: true, mode: 448 });
32378
- fs25.writeFileSync(file2, JSON.stringify(saved, null, 2), { mode: 384 });
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(fs25.readFileSync(handoffSnapshotPath(locationId2, base), "utf8"));
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 path23.join(base, "client-status", `${locationId2}-status.html`);
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(fs25.readFileSync(path23.join(base, "cockpit-state.json"), "utf8"));
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
- fs25.mkdirSync(dir, { recursive: true });
32404
- const file2 = path23.join(dir, fileName);
32405
- fs25.writeFileSync(file2, html2);
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(path23.dirname(localPath), path23.basename(localPath), html2);
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 ? path23.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
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 ? path23.join(placement.directory, fileName) : writeFileFor(placement.directory, fileName, html2);
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 fs25, path23;
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
- fs25 = __toESM(require("node:fs"));
32549
- path23 = __toESM(require("node:path"));
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 path24.join(appDataDir(), "build-obligations", id + ".json");
32587
+ return path23.join(appDataDir(), "build-obligations", id + ".json");
32629
32588
  }
32630
32589
  function readBuildObligations(id) {
32631
32590
  try {
32632
- const data = JSON.parse(fs26.readFileSync(fileFor(id), "utf8"));
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
- fs26.mkdirSync(path24.dirname(file2), { recursive: true });
32644
- fs26.writeFileSync(file2 + ".tmp", JSON.stringify([...merged.values()], null, 2), { mode: 384 });
32645
- fs26.renameSync(file2 + ".tmp", file2);
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
- fs26.mkdirSync(path24.dirname(file2), { recursive: true });
32671
- fs26.writeFileSync(file2 + ".tmp", JSON.stringify(remaining, null, 2), { mode: 384 });
32672
- fs26.renameSync(file2 + ".tmp", file2);
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 fs26, path24;
32633
+ var fs25, path23;
32675
32634
  var init_build_obligations = __esm({
32676
32635
  "src/build-obligations.ts"() {
32677
32636
  "use strict";
32678
- fs26 = __toESM(require("node:fs"));
32679
- path24 = __toESM(require("node:path"));
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, import_node_crypto9.createHash)("sha256").update(key).digest("hex");
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 import_node_crypto9, CLAUDE_REPAIR_MESSAGES;
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
- import_node_crypto9 = require("node:crypto");
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, import_node_crypto10.randomUUID)();
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, import_node_crypto10, SLUG2, MODEL, JOINED_FLAG, jobs, children;
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
- import_node_crypto10 = require("node:crypto");
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 path25.join(appDataDir(), "command-os-run-cost.jsonl");
32934
+ return path24.join(appDataDir(), "command-os-run-cost.jsonl");
32976
32935
  }
32977
32936
  function recordRunCost(rec, file2 = runCostPath()) {
32978
- fs27.mkdirSync(path25.dirname(file2), { recursive: true });
32979
- fs27.appendFileSync(file2, `${JSON.stringify(rec)}
32937
+ fs26.mkdirSync(path24.dirname(file2), { recursive: true });
32938
+ fs26.appendFileSync(file2, `${JSON.stringify(rec)}
32980
32939
  `, { mode: 384 });
32981
32940
  try {
32982
- fs27.chmodSync(file2, 384);
32941
+ fs26.chmodSync(file2, 384);
32983
32942
  } catch {
32984
32943
  }
32985
32944
  }
32986
32945
  function readRunCosts(file2 = runCostPath()) {
32987
32946
  try {
32988
- return fs27.readFileSync(file2, "utf8").split("\n").filter((l) => l.trim()).map((l) => {
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 fs27, path25;
32978
+ var fs26, path24;
33020
32979
  var init_run_cost = __esm({
33021
32980
  "src/run-cost.ts"() {
33022
32981
  "use strict";
33023
- fs27 = __toESM(require("fs"));
33024
- path25 = __toESM(require("path"));
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((resolve12, reject) => {
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) resolve12(text2);
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((q2) => {
33161
- const base = INTAKE_QUESTIONS.find((b) => b.key === q2.key);
33162
- return base && base.label !== q2.label ? { ...q2, label: base.label } : q2;
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 clean = (s) => s.replace(/[\s.,:;|–—-]+$/, "").trim();
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 = clean(m[1]);
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 tokens = entry.split(/,?\s+and\s+/i);
33608
- if (tokens.length < 2) return [entry];
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 tokens) {
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 q2 of questions) {
33864
- if (q2.field.kind !== "custom") continue;
33865
- const id = byFieldKey.get(expectedFieldKey(q2).toLowerCase());
33866
- if (id) map[q2.key] = id;
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 = (q2) => {
33929
- const rawKey = q2.field.kind === "standard" ? q2.field.tag : opts.fieldMap[q2.key];
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((q2) => q2.briefKind === "staffList" && q2.briefPath);
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 q2 of questions) {
33944
- if (!q2.briefPath || q2.briefKind === "identity") continue;
33902
+ for (const q3 of questions) {
33903
+ if (!q3.briefPath || q3.briefKind === "identity") continue;
33945
33904
  let value;
33946
- if (q2 === staffQ) {
33905
+ if (q3 === staffQ) {
33947
33906
  value = staff;
33948
- } else if (q2.briefKind === "calendarList") {
33949
- const s = firstString(rawFor(q2));
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(q2), q2.briefKind, warnings);
33915
+ value = coerceValue(rawFor(q3), q3.briefKind, warnings);
33957
33916
  }
33958
- setPath(brief, q2.briefPath, value);
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
- path26.join(__dirname, "..", "skills", "blueprint", "presets"),
34586
+ path25.join(__dirname, "..", "skills", "blueprint", "presets"),
34628
34587
  // dist/ → package root
34629
- path26.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
34588
+ path25.join(__dirname, "..", "..", "skills", "blueprint", "presets"),
34630
34589
  // src/intake-to-build in dev
34631
- path26.join(process.cwd(), "skills", "blueprint", "presets")
34590
+ path25.join(process.cwd(), "skills", "blueprint", "presets")
34632
34591
  ];
34633
- return candidates.find((c) => fs28.existsSync(c)) ?? null;
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(fs28.readFileSync(path26.join(dir, `${base}.preset.json`), "utf8"));
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 = fs28.readdirSync(dir).filter((f) => f.endsWith(".preset.json")).map((f) => f.replace(/\.preset\.json$/, ""));
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 fs28, path26, PRESET_TOKEN_ROOTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE, BRIEF_CALENDAR_TYPE_MAP;
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
- fs28 = __toESM(require("fs"));
34685
- path26 = __toESM(require("path"));
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 stop = monthly * a("recovery");
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: stop * 12,
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(q2) {
35005
- UNIT[q2[0]] = q2[4];
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, q2, today2) {
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 = q2.ask(id);
35040
- const area = q2.area(id);
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 q2.areaOrder()) {
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 = q2.assumption?.(k) ?? k.replace(/_/g, " ");
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 buildNote(state, computed, q2, today2) {
35086
- const payload = JSON.stringify({ v: 1, savedAt: today2, state, computed });
35087
- return `${sanitiseHuman(renderHuman(state, computed, q2, today2))}
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
- if (!Array.isArray(notes)) return null;
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) continue;
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
- const parsed2 = bodies.length === 1 ? parseNote(bodies[0]) : parseNotes(bodies);
35216
- if (parsed2) return parsed2;
35203
+ out.push(bodies.length === 1 ? parseNote(bodies[0]) : parseNotes(bodies));
35217
35204
  }
35218
- return null;
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(q2, value) {
35295
- const fixed = q2.options !== void 0 || q2.field.kind === "custom" && ["SINGLE_OPTIONS", "MULTIPLE_OPTIONS", "CHECKBOX"].includes(q2.field.dataType);
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 q2.field.kind === "custom" && ["MULTIPLE_OPTIONS", "CHECKBOX"].includes(q2.field.dataType) && value.every((choice) => q2.options?.includes(choice) === true);
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 q2.options?.includes(value) === true;
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 q2 = questions.find((q3) => q3.key === mapping.target);
35313
+ const q3 = questions.find((q4) => q4.key === mapping.target);
35327
35314
  const value = values[mapping.target]?.trim();
35328
- if (!q2 || !value || !matchesQuestionChoices(q2, value)) {
35329
- leftForMember.push({ key: mapping.target, question: q2?.label ?? mapping.target, reason: !q2 ? "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." });
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[q2.label] = q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS" ? [value] : value;
35333
- filled.push(q2.label);
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 q2 = questions.find((q3) => q3.label === label);
35352
- const previous = answers[label] ?? answers[q2.key];
35338
+ const q3 = questions.find((q4) => q4.label === label);
35339
+ const previous = answers[label] ?? answers[q3.key];
35353
35340
  let merged = value;
35354
- if ((q2.key === "anything_else" || q2.key === "price_points") && previous && !String(previous).includes(String(value))) merged = `${previous}
35341
+ if ((q3.key === "anything_else" || q3.key === "price_points") && previous && !String(previous).includes(String(value))) merged = `${previous}
35355
35342
  ${value}`;
35356
- else if ((q2.key === "anything_else" || q2.key === "price_points") && previous) merged = previous;
35357
- if (!matchesQuestionChoices(q2, merged)) {
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: q2.key, question: label, reason: "The combined answer does not match this intake\u2019s choices. Your saved answer was left unchanged." });
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 (q2.key !== label) delete answers[q2.key];
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 q2 of [...INTAKE_TAGS, ...formName ? [formName] : []]) {
35438
+ for (const q3 of [...INTAKE_TAGS, ...formName ? [formName] : []]) {
35452
35439
  try {
35453
- const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q2, limit: 20 } });
35454
- for (const c of found.contacts ?? []) if ((c.tags ?? []).some((t) => INTAKE_TAGS.includes(t)) || q2 === formName) ids.add(c.id);
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(q2) {
35538
- if (q2.field.kind !== "standard") throw new Error(`buildStandardFormField: ${q2.key} is not standard`);
35539
- const tag = q2.field.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: q2.label,
35546
- placeholder: q2.placeholder ?? q2.label,
35547
- required: q2.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(q2, record, locationId2, position2) {
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: q2.label,
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: q2.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: q2.placeholder ?? "",
35566
+ placeholder: q3.placeholder ?? "",
35580
35567
  position: position2,
35581
- required: q2.required,
35568
+ required: q3.required,
35582
35569
  showInForms: true,
35583
35570
  standard: false,
35584
35571
  type
35585
35572
  };
35586
- const options = record.picklistOptions ?? q2.options ?? void 0;
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 q2 of questions) {
35636
- if (q2.section !== lastSection) {
35637
- fields.push(buildHeaderField(q2.sectionLabel, headerIndex++));
35638
- lastSection = q2.section;
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 (q2.field.kind === "standard") {
35641
- fields.push(buildStandardFormField(q2));
35627
+ if (q3.field.kind === "standard") {
35628
+ fields.push(buildStandardFormField(q3));
35642
35629
  } else {
35643
- const record = opts.resolved.get(q2.key);
35630
+ const record = opts.resolved.get(q3.key);
35644
35631
  if (!record) continue;
35645
- fields.push(buildCustomFormField(q2, record, opts.locationId, position2 += 50));
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 digits(s) {
35680
- return String(s ?? "").replace(/[^0-9]/g, "");
35681
- }
35682
- function valueIsInQuote(value, quote) {
35683
- const v = loose(value);
35684
- const q2 = loose(quote);
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 q2 = String(quote ?? "").trim();
35692
+ const q3 = String(quote ?? "").trim();
35694
35693
  if (v === "") return { verdict: "refused", because: "no value was given." };
35695
- if (q2 === "") return { verdict: "refused", because: "no quote was given, so there is nothing tying this answer to what they said." };
35696
- if (q2.length > MAX_QUOTE) return { verdict: "refused", because: `the quote is ${q2.length} characters. A quote that long is a summary, not a citation.` };
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.includes(loose(q2))) return { verdict: "refused", because: "that quote does not appear in the transcript." };
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 q2 of [INTAKE_CONTACT_TAG, INTAKE_FORM_NAME2]) {
35846
- const found = await client.get("/contacts/", { params: { locationId: locationId2, query: q2, limit: 20 } });
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 q2 of questions) {
35981
- const raw = proposed[q2.label];
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 (q2.options?.length) {
35986
- if (values.some((v) => !q2.options.includes(v)) || !q2.multi && values.length !== 1) continue;
35987
- result[q2.label] = q2.multi ? [...new Set(values)] : values[0];
35988
- } else if (values.length === 1) result[q2.label] = values[0];
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 path27.join(base, "plans");
36033
+ return path26.join(base, "plans");
36009
36034
  }
36010
36035
  function savePlanRecord(rec, base) {
36011
36036
  const dir = plansDir(base);
36012
- fs29.mkdirSync(dir, { recursive: true, mode: 448 });
36013
- const file2 = path27.join(dir, `${rec.locationId}.json`);
36014
- fs29.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 });
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(fs29.readFileSync(path27.join(plansDir(base), `${locationId2}.json`), "utf8"));
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 fs29, path27;
36077
+ var fs28, path26;
36053
36078
  var init_plan_store = __esm({
36054
36079
  "src/plan-store.ts"() {
36055
36080
  "use strict";
36056
- fs29 = __toESM(require("fs"));
36057
- path27 = __toESM(require("path"));
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 path28.join(__dirname, "index.js");
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
- fs30.accessSync(systemApps, fs30.constants.W_OK);
37753
+ fs29.accessSync(systemApps, fs29.constants.W_OK);
37729
37754
  base = "";
37730
37755
  } catch {
37731
37756
  }
37732
- const appDir = base ? path28.join(home, "Applications", "Command OS.app") : path28.join(systemApps, "Command OS.app");
37733
- const macOSDir = path28.join(appDir, "Contents", "MacOS");
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: path28.join(macOSDir, "command-os"), contents: script, executable: true },
37763
- { path: path28.join(appDir, "Contents", "Info.plist"), contents: plist, executable: false }
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 = path28.join(home, "Desktop", "Command OS.cmd");
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 = path28.join(home, ".local", "share", "applications", "command-os.desktop");
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) => fs30.existsSync(f.path));
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
- fs30.mkdirSync(path28.dirname(f.path), { recursive: true });
37817
- fs30.writeFileSync(f.path, f.contents, { mode: f.executable ? 493 : 420 });
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 fs30, os4, path28;
37862
+ var fs29, os4, path27;
37838
37863
  var init_launcher = __esm({
37839
37864
  "src/launcher.ts"() {
37840
37865
  "use strict";
37841
- fs30 = __toESM(require("fs"));
37866
+ fs29 = __toESM(require("fs"));
37842
37867
  os4 = __toESM(require("os"));
37843
- path28 = __toESM(require("path"));
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" ? path29.join(env2.LOCALAPPDATA?.trim() || path29.join(home, "AppData", "Local"), "npm-cache") : path29.join(home, ".npm");
37851
- return path29.join(cache, "_npx");
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 = fs31.readdirSync(npxDir, { withFileTypes: true });
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
- fs31.statSync(p);
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 = path29.join(npxDir, entry.name);
37871
- const pkg2 = path29.join(dir, PACKAGE_SUBDIR);
37895
+ const dir = path28.join(npxDir, entry.name);
37896
+ const pkg2 = path28.join(dir, PACKAGE_SUBDIR);
37872
37897
  try {
37873
- if (!fs31.statSync(dir).isDirectory() || !fs31.statSync(pkg2).isDirectory()) continue;
37898
+ if (!fs30.statSync(dir).isDirectory() || !fs30.statSync(pkg2).isDirectory()) continue;
37874
37899
  } catch {
37875
37900
  continue;
37876
37901
  }
37877
- if (gone(path29.join(dir, "package.json")) || gone(path29.join(pkg2, "package.json"))) damaged.push(dir);
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 fs31, os5, path29, PACKAGE_SUBDIR;
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
- fs31 = __toESM(require("node:fs"));
37915
+ fs30 = __toESM(require("node:fs"));
37891
37916
  os5 = __toESM(require("node:os"));
37892
- path29 = __toESM(require("node:path"));
37893
- PACKAGE_SUBDIR = path29.join("node_modules", "@elitedcs", "ghl-mcp");
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) => fs32.renameSync(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(path30.join(home, "Library", "Application Support", "Claude", file2));
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 ?? path30.join(home, "AppData", "Roaming");
37930
- candidates.push(path30.join(appData, "Claude", file2));
37931
- const localAppData = opts?.localAppData ?? process.env.LOCALAPPDATA ?? path30.join(home, "AppData", "Local");
37932
- const packagesDir = path30.join(localAppData, "Packages");
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 fs32.readdirSync(packagesDir)) {
37959
+ for (const entry of fs31.readdirSync(packagesDir)) {
37935
37960
  if (entry.startsWith("Claude_")) {
37936
- candidates.push(path30.join(packagesDir, entry, "LocalCache", "Roaming", "Claude", file2));
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(path30.join(home, ".config", "Claude", file2));
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 = path30.resolve(explicitPath);
37949
- return { configPath: p, exists: fs32.existsSync(p) };
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) => fs32.existsSync(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 = fs32.lstatSync(configPath);
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 = fs32.realpathSync(configPath);
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 = path30.relative(path30.resolve(home), real);
37984
- const outsideHome = rel.startsWith("..") || path30.isAbsolute(rel);
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 = fs32.readFileSync(configPath);
37995
- const stat = fs32.statSync(configPath);
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 (!fs32.existsSync(base)) return base;
38094
+ if (!fs31.existsSync(base)) return base;
38070
38095
  for (let i = 2; ; i++) {
38071
38096
  const candidate = `${base}-${i}`;
38072
- if (!fs32.existsSync(candidate)) return candidate;
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
- fs32.writeFileSync(backupPath, originalBytes);
38079
- const readBack = fs32.readFileSync(backupPath);
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
- fs32.rmSync(backupPath, { force: true });
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, import_node_crypto11.createHash)("sha256").update(bytes).digest("hex");
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 = fs32.statSync(configPath);
38129
+ const st = fs31.statSync(configPath);
38105
38130
  if (st.size === baseline.size && st.mtimeMs === baseline.mtimeMs) {
38106
- ok = sha256(fs32.readFileSync(configPath)) === baseline.hash;
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 = path30.dirname(opts.configPath);
38122
- const tempPath = path30.join(dir, `.${path30.basename(opts.configPath)}.tmp-${process.pid}-${(0, import_node_crypto11.randomBytes)(4).toString("hex")}`);
38123
- const fd = fs32.openSync(tempPath, "w");
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
- fs32.writeFileSync(fd, opts.newBytes);
38126
- fs32.fsyncSync(fd);
38150
+ fs31.writeFileSync(fd, opts.newBytes);
38151
+ fs31.fsyncSync(fd);
38127
38152
  } finally {
38128
- fs32.closeSync(fd);
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
- fs32.rmSync(tempPath, { force: true });
38178
+ fs31.rmSync(tempPath, { force: true });
38154
38179
  } catch {
38155
38180
  }
38156
38181
  throw e;
38157
38182
  }
38158
38183
  try {
38159
- const dirFd = fs32.openSync(dir, "r");
38184
+ const dirFd = fs31.openSync(dir, "r");
38160
38185
  try {
38161
- fs32.fsyncSync(dirFd);
38186
+ fs31.fsyncSync(dirFd);
38162
38187
  } finally {
38163
- fs32.closeSync(dirFd);
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 = fs32.existsSync(configPath);
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) fs32.mkdirSync(path30.dirname(configPath), { recursive: true });
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 fs32, os6, path30, import_node_crypto11, 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;
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
- fs32 = __toESM(require("node:fs"));
38301
+ fs31 = __toESM(require("node:fs"));
38277
38302
  os6 = __toESM(require("node:os"));
38278
- path30 = __toESM(require("node:path"));
38279
- import_node_crypto11 = require("node:crypto");
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 = path31.join(dir, `.write-probe.${process.pid}.${(0, import_crypto2.randomBytes)(4).toString("hex")}`);
38313
- fs33.writeFileSync(probe, "ok");
38314
- fs33.unlinkSync(probe);
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, fs33, path31, import_crypto2, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
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
- fs33 = __toESM(require("fs"));
38589
- path31 = __toESM(require("path"));
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 path32.join(appDataDir(), "intake-overlay.json");
39160
+ return path31.join(appDataDir(), "intake-overlay.json");
39136
39161
  }
39137
39162
  function readOverlay() {
39138
39163
  try {
39139
- const raw = JSON.parse(fs34.readFileSync(overlayPath(), "utf8"));
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
- fs34.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
39172
+ fs33.writeFileSync(overlayPath(), JSON.stringify(layer, null, 2), { mode: 384 });
39148
39173
  }
39149
- var fs34, path32;
39174
+ var fs33, path31;
39150
39175
  var init_intake_overlay = __esm({
39151
39176
  "src/intake-overlay.ts"() {
39152
39177
  "use strict";
39153
- fs34 = __toESM(require("fs"));
39154
- path32 = __toESM(require("path"));
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 q2 = opts.answered[a];
39311
- var numeric = q2.unit !== void 0 ? q2.unit !== "text" : isFigure(q2.value);
39312
- out.push("<tr><td>" + esc7(q2.ask) + '</td><td class="' + (numeric ? "num" : "said") + '">' + (q2.unknown ? '<span class="unk">you did not know</span>' : esc7(q2.value)) + "</td></tr>");
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
- const upserted = await client.post("/contacts/upsert", {
39545
- body: { ...contactBody, locationId: locationId2 }
39546
- });
39547
- const contactId = upserted?.contact?.id ?? upserted?.id;
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 bodies = buildNoteBodies(buildNote(state, computed, LOOKUP, today()));
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
- "Turn a recorded conversation into assessment answers, WITHOUT letting anything into the record that the owner did not say. You read the transcript and propose answers; each one must carry the verbatim words it came from. This checks every quote actually appears in the transcript and throws out the ones that do not, and holds back anything you worked out rather than heard so the operator can confirm it. Use it after a coffee-shop meeting, a Zoom call or a Plaud recording.",
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
- 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.")
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 ({ transcript, proposed, existing }) => {
39735
- const gated = gateExtraction(transcript, proposed, QUESTION_UNITS);
39736
- const merged = mergeAccepted(existing ?? {}, gated);
39737
- return {
39738
- applied: gated.accepted.length,
39739
- answers: merged,
39740
- confirmBeforeTheseCount: gated.held.map((h) => ({
39741
- question: ASK.get(h.questionId) ?? h.questionId,
39742
- proposedValue: h.value,
39743
- theyActuallySaid: h.quote,
39744
- howItWasWorkedOut: h.reasoning ?? null,
39745
- why: h.because
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((q2) => {
39774
- const id = q2[0];
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((q2) => state.skip[q2[0]] ? { ask: q2[2], unknown: true, unit: q2[4] } : { ask: q2[2], value: String(state.ans[q2[0]]), unit: q2[4] });
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, import_node_crypto12.randomBytes)(4).toString("hex")}.html`);
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, import_node_crypto12, import_node_fs6, import_node_os, import_node_path6, QUESTION_UNITS, AREA_TITLE, ASK, AREA_OF, ASSUMPTION_LABEL, LOOKUP, StateSchema;
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
- import_node_crypto12 = require("node:crypto");
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((q2) => [q2[0], q2[4]]));
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((q2) => [q2[0], q2[2]]));
39846
- AREA_OF = new Map(Q.map((q2) => [q2[0], q2[1]]));
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((q2) => {
40182
- const base = INTAKE_QUESTIONS.find((b) => b.key === q2.key);
40183
- return base && base.label !== q2.label;
40184
- }).map((q2) => ({ key: q2.key, label: q2.label, from: INTAKE_QUESTIONS.find((b) => b.key === q2.key)?.label })),
40185
- removedKeys: INTAKE_QUESTIONS.filter((b) => !composed.some((q2) => q2.key === b.key)).map((b) => b.key),
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(!st.some(s=>s==="done"))div.appendChild(preflightCard(c));
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=i<=1||sandbox||Boolean((state.buildApproved||{})[c.locationId]);
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
- let act=(activeId&&board.some(c=>c.locationId===activeId))?activeId:null;
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=board.find(c=>c.locationId===act);
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
- if(parked.length){const gh=document.createElement("div");gh.className="grouphead";
41834
- gh.textContent=(showParked?"\u25BE":"\u25B8")+" Templates & hidden ("+parked.length+")";
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 parked)list2.appendChild(acctRow(c,state,run));m.appendChild(list2)}}}
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((q2) => q2.required && !String(normalizedAnswers?.[q2.label] ?? normalizedAnswers?.[q2.key] ?? "").trim());
42584
- if (required.length) throw new Error("Complete these required intake answers: " + required.map((q2) => q2.label).join(", ") + ".");
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((q2) => ({ key: q2.key, label: q2.label, sectionLabel: q2.sectionLabel, required: q2.required, base: INTAKE_QUESTIONS.some((b) => b.key === q2.key) }))
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((q2) => ({ key: q2.key, contactTag: q2.field.kind === "standard" ? q2.field.tag : void 0, label: q2.label, sectionLabel: q2.sectionLabel, required: q2.required, options: q2.options ?? null, placeholder: q2.placeholder ?? "", multi: q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS" }));
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((q2) => q2.required && !String(answers[q2.label] ?? answers[q2.key] ?? "").trim()).map((q2) => q2.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 }));
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((q2) => q2.field.kind === "standard").map((q2) => q2.label));
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((resolve12) => {
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", () => resolve12(recorderAllowPatterns(buf)));
42752
- ls.on("error", () => resolve12([]));
43309
+ ls.on("close", () => resolve13(recorderAllowPatterns(buf)));
43310
+ ls.on("error", () => resolve13([]));
42753
43311
  setTimeout(() => {
42754
43312
  ls.kill("SIGKILL");
42755
- resolve12(recorderAllowPatterns(buf));
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((q2) => q2.label),
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((q2) => ({ label: q2.label, options: q2.options, multi: q2.field.kind === "custom" && q2.field.dataType === "MULTIPLE_OPTIONS", dataType: q2.field.kind === "custom" ? q2.field.dataType : q2.field.tag }))
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 q2 = new URL(req.url || "/", "http://x").searchParams;
43090
- const locationId2 = q2.get("locationId") || "";
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: q2.get("agency") || st.agencyName, accent: q2.get("accent") || st.agencyAccent }));
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((resolve12, reject) => {
44119
+ await new Promise((resolve13, reject) => {
43562
44120
  server2.once("error", reject);
43563
- server2.listen(port, "127.0.0.1", resolve12);
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((resolve12) => {
43590
- const stop = () => server2.close(() => resolve12(0));
43591
- process.on("SIGINT", stop);
43592
- process.on("SIGTERM", stop);
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 tokens = registry2.listLocations().map((loc) => ({
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 = tokens.map((loc) => {
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 (${tokens.length} locations):
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((resolve12) => setTimeout(resolve12, ms));
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 bullet(lines) {
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 bullet(section2.map(render));
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
- ${bullet(
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
- ${bullet(ownership.unknown)}` : "Every asset found in this account has a recorded owner.";
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
- ${bullet(ownership.unreadable)}
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((resolve12) => setTimeout(resolve12, ms));
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 q2 = [
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
- q2.push(`filterParams[starRating][${i}][value]=${value}`);
45798
- q2.push(`filterParams[starRating][${i}][condition]=${condition}`);
46356
+ q3.push(`filterParams[starRating][${i}][value]=${value}`);
46357
+ q3.push(`filterParams[starRating][${i}][condition]=${condition}`);
45799
46358
  });
45800
- q2.push(`sortParams[dateAdded]=-1`);
45801
- q2.push(`pageNumber=${opts.pageNumber ?? 1}`);
45802
- q2.push(`pageSize=${opts.pageSize ?? 10}`);
45803
- const query = q2.map((p) => {
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((resolve12) => setTimeout(resolve12, ms));
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 resolve12 = (text2) => {
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 resolve12(obj);
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 inside(inner, outer, inset = 0) {
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) => !inside(e.box, s.box, inset));
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 (!inside(l.box, s.box, inset)) bad.push(`${slideName(s)} logo inside the safe band`);
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 digits2 = (x) => x.replace(/[,\s]/g, "");
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: digits2(m[0]), units: /* @__PURE__ */ new Set([...unitAfter(line3, m.index ?? 0), ...norm4(agg.label).split(" ").filter((w) => w && !STOP.has(w))]) }));
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 = `${digits2(m[0])}@${norm4(text2).slice(0, 40)}`;
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 === digits2(m[0]));
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 clean;
50779
+ let clean2;
50220
50780
  try {
50221
- clean = sanitizeSvg(a.logoSvg);
50781
+ clean2 = sanitizeSvg(a.logoSvg);
50222
50782
  } catch (e) {
50223
50783
  throw new Error(`${e.message} Nothing was changed.`);
50224
50784
  }
50225
- a.logoSvg = clean;
50226
- d.logo = { inlineSvg: clean, whiteFillFixed: false, source: `set by ${stamp}`, rights: "declared" };
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, import_node_crypto13.createHash)("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 16);
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, q2) => p.at < q2.at ? -1 : p.at > q2.at ? 1 : 0)) {
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, q2) => q2.at > p.at ? q2 : 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 import_node_crypto13, isExisting, FINDINGS_STALE_AFTER_MINUTES;
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
- import_node_crypto13 = require("node:crypto");
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 q2 = who.trim();
52299
- if (!q2) return { error: "Name who you mean \u2014 either the way you refer to them, or their account id." };
52300
- const byId2 = roster.find((r) => r.locationId === q2);
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() === q2.toLowerCase());
52303
- const loose2 = hits.length ? hits : roster.filter((r) => r.name?.toLowerCase().includes(q2.toLowerCase()));
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 "${q2}": ${loose2.map((r) => r.name).join(", ")}. Use the full name.` };
52306
- return { error: `Nothing called "${q2}" is on your board or in your records.` };
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 digits2 = (word.match(/\d/g) ?? []).length;
53121
+ const digits = (word.match(/\d/g) ?? []).length;
52562
53122
  const letters = (word.match(/[A-Za-z]/g) ?? []).length;
52563
- if (digits2 < 2 || letters < 2) return false;
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((resolve12) => setTimeout(resolve12, ms))) {
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 inside2 = g.items.length === 1 ? "inside it" : "inside them";
53730
- L.push(` (plus the ${children2.length} ${PLURAL[kind].toLowerCase()} ${inside2}, which GoHighLevel removes at the same time)`);
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(q2, records) {
54924
- const wantKey = expectedFieldKey(q2).toLowerCase();
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(q2.label).toLowerCase();
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((q2) => ({
55553
- key: q2.key,
55554
- label: q2.label,
55555
- section: q2.sectionLabel,
55556
- required: q2.required,
55557
- fieldKind: q2.field.kind,
55558
- dataType: q2.field.kind === "custom" ? q2.field.dataType : void 0,
55559
- standardTag: q2.field.kind === "standard" ? q2.field.tag : void 0,
55560
- expectedFieldKey: q2.field.kind === "custom" ? expectedFieldKey(q2) : void 0,
55561
- options: q2.options,
55562
- briefPath: q2.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((q2) => q2.field.kind === "custom");
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 q2 of cqs) {
56171
- const found = findRecordForQuestion(q2, existing);
56730
+ for (const q3 of cqs) {
56731
+ const found = findRecordForQuestion(q3, existing);
56172
56732
  if (found) {
56173
- reused.push(q2.label);
56733
+ reused.push(q3.label);
56174
56734
  } else {
56175
- created.push(q2.label);
56735
+ created.push(q3.label);
56176
56736
  if (!dryRun) {
56177
56737
  const body = {
56178
- name: intakeFieldName(q2.label),
56179
- dataType: q2.field.kind === "custom" ? q2.field.dataType : "TEXT",
56738
+ name: intakeFieldName(q3.label),
56739
+ dataType: q3.field.kind === "custom" ? q3.field.dataType : "TEXT",
56180
56740
  model: "contact"
56181
56741
  };
56182
- if (q2.options && q2.field.kind === "custom") {
56183
- const dt = q2.field.dataType;
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 = [...q2.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 q2 of cqs) {
56200
- const rec = findRecordForQuestion(q2, after);
56759
+ for (const q3 of cqs) {
56760
+ const rec = findRecordForQuestion(q3, after);
56201
56761
  if (!rec) {
56202
- missing = q2;
56762
+ missing = q3;
56203
56763
  break;
56204
56764
  }
56205
- resolved2.set(q2.key, rec);
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((q2) => q2.field.kind === "standard").map((q2) => q2.label),
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 import_node_crypto14 = require("node:crypto");
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, import_node_crypto14.createHash)("sha256").update(buf).digest("hex");
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));