sproutkit-code 1.13.0 → 1.15.0

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.
Files changed (2) hide show
  1. package/dist/index.js +418 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -407,13 +407,19 @@ var require_db = __commonJS({
407
407
  } catch {
408
408
  }
409
409
  }
410
+ for (const col of ["receptionist_id TEXT", "receptionist_url TEXT", "receptionist_price_pence INTEGER", "receptionist_line TEXT"]) {
411
+ try {
412
+ db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
413
+ } catch {
414
+ }
415
+ }
410
416
  for (const col of ["pitch_video_url TEXT", "pitch_poster_url TEXT", "pitch_script TEXT"]) {
411
417
  try {
412
418
  db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
413
419
  } catch {
414
420
  }
415
421
  }
416
- for (const col of ["domain TEXT", "domain_pence INTEGER NOT NULL DEFAULT 0"]) {
422
+ for (const col of ["domain TEXT", "domain_pence INTEGER NOT NULL DEFAULT 0", "receptionist_pence INTEGER NOT NULL DEFAULT 0"]) {
417
423
  try {
418
424
  db2.exec(`ALTER TABLE deals ADD COLUMN ${col}`);
419
425
  } catch {
@@ -81620,6 +81626,7 @@ var require_builder = __commonJS({
81620
81626
  return mod && mod.__esModule ? mod : { "default": mod };
81621
81627
  };
81622
81628
  Object.defineProperty(exports2, "__esModule", { value: true });
81629
+ exports2.siteSourceFor = siteSourceFor;
81623
81630
  exports2.buildAndDeploySite = buildAndDeploySite;
81624
81631
  exports2.redeployCmsOnly = redeployCmsOnly;
81625
81632
  exports2.deployBookingOnly = deployBookingOnly2;
@@ -81835,6 +81842,9 @@ var require_builder = __commonJS({
81835
81842
  } catch {
81836
81843
  }
81837
81844
  }
81845
+ function siteSourceFor(leadId) {
81846
+ return loadSnapshot(leadId)?.find((f) => f.file === "src/App.jsx")?.data ?? null;
81847
+ }
81838
81848
  function loadSnapshot(leadId) {
81839
81849
  const dir = snapshotDir(leadId);
81840
81850
  if (!fs4.existsSync(dir))
@@ -84468,6 +84478,8 @@ var require_pitch_text = __commonJS({
84468
84478
  "use strict";
84469
84479
  Object.defineProperty(exports2, "__esModule", { value: true });
84470
84480
  exports2.withVideo = withVideo;
84481
+ exports2.withReceptionist = withReceptionist;
84482
+ exports2.prettyNumber = prettyNumber;
84471
84483
  function withVideo(message, videoUrl) {
84472
84484
  if (!videoUrl)
84473
84485
  return message.replace(/\n?[^\n]*\{video\}[^\n]*/g, "");
@@ -84483,6 +84495,394 @@ ${line}`;
84483
84495
  lines.splice(at + 1, 0, "", line);
84484
84496
  return lines.join("\n");
84485
84497
  }
84498
+ function withReceptionist(message, url, demoNumber, businessName) {
84499
+ const what = demoNumber ? `ring ${prettyNumber(demoNumber)} from your business phone and it'll answer as ${businessName ?? "you"}` : url ? `give it a call here: ${url}` : null;
84500
+ if (!what)
84501
+ return message.replace(/\n?[^\n]*\{receptionist\}[^\n]*/g, "");
84502
+ if (message.includes("{receptionist}"))
84503
+ return message.replace(/\{receptionist\}/g, demoNumber ? prettyNumber(demoNumber) : url);
84504
+ const line = `I've also set up an AI receptionist that would answer your calls 24/7 when you can't \u2014 ${what}.`;
84505
+ const lines = message.split("\n");
84506
+ const at = lines.findIndex((l) => /https?:\/\//.test(l));
84507
+ if (at < 0)
84508
+ return `${message}
84509
+
84510
+ ${line}`;
84511
+ lines.splice(at + 1, 0, "", line);
84512
+ return lines.join("\n");
84513
+ }
84514
+ function prettyNumber(e164) {
84515
+ const d = e164.replace(/[^\d+]/g, "");
84516
+ if (d.startsWith("+44")) {
84517
+ const n = "0" + d.slice(3);
84518
+ if (/^0(1\d1|11\d)/.test(n))
84519
+ return `${n.slice(0, 4)} ${n.slice(4, 7)} ${n.slice(7)}`;
84520
+ if (/^02/.test(n))
84521
+ return `${n.slice(0, 3)} ${n.slice(3, 7)} ${n.slice(7)}`;
84522
+ return `${n.slice(0, 5)} ${n.slice(5)}`;
84523
+ }
84524
+ if (d.startsWith("+1") && d.length === 12)
84525
+ return `(${d.slice(2, 5)}) ${d.slice(5, 8)}-${d.slice(8)}`;
84526
+ return e164;
84527
+ }
84528
+ }
84529
+ });
84530
+
84531
+ // ../desktop/dist/main/lib/license.js
84532
+ var require_license = __commonJS({
84533
+ "../desktop/dist/main/lib/license.js"(exports2) {
84534
+ "use strict";
84535
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
84536
+ return mod && mod.__esModule ? mod : { "default": mod };
84537
+ };
84538
+ Object.defineProperty(exports2, "__esModule", { value: true });
84539
+ exports2.clearLicense = clearLicense;
84540
+ exports2.getActiveLicenseKey = getActiveLicenseKey;
84541
+ exports2.activate = activate2;
84542
+ exports2.checkLicense = checkLicense;
84543
+ exports2.startBackgroundValidation = startBackgroundValidation;
84544
+ exports2.stopBackgroundValidation = stopBackgroundValidation;
84545
+ var electron_1 = require_electron();
84546
+ var crypto_1 = require("crypto");
84547
+ var fs_1 = __importDefault(require("fs"));
84548
+ var os_1 = __importDefault(require("os"));
84549
+ var path_1 = __importDefault(require("path"));
84550
+ var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
84551
+ var VALIDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
84552
+ function statePath() {
84553
+ return path_1.default.join(electron_1.app.getPath("userData"), "license.json");
84554
+ }
84555
+ function loadPersisted() {
84556
+ try {
84557
+ const raw = fs_1.default.readFileSync(statePath(), "utf-8");
84558
+ return JSON.parse(raw);
84559
+ } catch {
84560
+ return null;
84561
+ }
84562
+ }
84563
+ function savePersisted(data) {
84564
+ fs_1.default.writeFileSync(statePath(), JSON.stringify(data, null, 2), { encoding: "utf-8" });
84565
+ }
84566
+ function clearLicense() {
84567
+ try {
84568
+ fs_1.default.unlinkSync(statePath());
84569
+ } catch {
84570
+ }
84571
+ }
84572
+ function getDeviceId() {
84573
+ const ifs = Object.values(os_1.default.networkInterfaces()).flat().filter((i) => !!i && !i.internal).map((i) => i.mac).filter((mac) => mac && mac !== "00:00:00:00:00:00");
84574
+ const seed = `${ifs[0] ?? "unknown"}|${os_1.default.hostname()}`;
84575
+ return (0, crypto_1.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
84576
+ }
84577
+ function getStoredKey() {
84578
+ const p = loadPersisted();
84579
+ if (!p)
84580
+ return null;
84581
+ try {
84582
+ return electron_1.safeStorage.decryptString(Buffer.from(p.encryptedKey, "base64"));
84583
+ } catch {
84584
+ return null;
84585
+ }
84586
+ }
84587
+ function getActiveLicenseKey() {
84588
+ return getStoredKey();
84589
+ }
84590
+ function getApiBaseUrl() {
84591
+ return process.env.SPROUTKIT_API_BASE_URL ?? "https://sproutkit.app";
84592
+ }
84593
+ async function callValidateApi(key) {
84594
+ try {
84595
+ const r = await fetch(`${getApiBaseUrl()}/api/licenses/validate`, {
84596
+ method: "POST",
84597
+ headers: { "Content-Type": "application/json" },
84598
+ body: JSON.stringify({ key, deviceId: getDeviceId() }),
84599
+ signal: AbortSignal.timeout(8e3)
84600
+ });
84601
+ if (!r.ok)
84602
+ return null;
84603
+ const parsed = await r.json();
84604
+ if (!parsed.valid && parsed.reason === "unavailable")
84605
+ return null;
84606
+ return parsed;
84607
+ } catch {
84608
+ return null;
84609
+ }
84610
+ }
84611
+ async function activate2(key) {
84612
+ const result = await callValidateApi(key.trim());
84613
+ if (!result) {
84614
+ return { status: "missing", key };
84615
+ }
84616
+ if (!result.valid) {
84617
+ return {
84618
+ status: result.reason === "revoked" ? "revoked" : result.reason === "refunded" ? "refunded" : result.reason === "expired" ? "expired" : "missing",
84619
+ key,
84620
+ email: result.email
84621
+ };
84622
+ }
84623
+ const encrypted = electron_1.safeStorage.encryptString(key.trim()).toString("base64");
84624
+ savePersisted({
84625
+ encryptedKey: encrypted,
84626
+ email: result.email,
84627
+ lastValidatedAt: Date.now(),
84628
+ plan: result.plan,
84629
+ renewsAt: result.renewsAt ?? null
84630
+ });
84631
+ return {
84632
+ status: "active",
84633
+ key,
84634
+ email: result.email,
84635
+ lastValidatedAt: Date.now(),
84636
+ plan: result.plan,
84637
+ renewsAt: result.renewsAt ?? null
84638
+ };
84639
+ }
84640
+ async function checkLicense() {
84641
+ const key = getStoredKey();
84642
+ if (!key)
84643
+ return { status: "missing" };
84644
+ const persisted = loadPersisted();
84645
+ const result = await callValidateApi(key);
84646
+ if (result) {
84647
+ if (result.valid) {
84648
+ savePersisted({
84649
+ encryptedKey: persisted.encryptedKey,
84650
+ email: result.email,
84651
+ lastValidatedAt: Date.now(),
84652
+ plan: result.plan,
84653
+ renewsAt: result.renewsAt ?? null
84654
+ });
84655
+ return {
84656
+ status: "active",
84657
+ key,
84658
+ email: result.email,
84659
+ lastValidatedAt: Date.now(),
84660
+ plan: result.plan,
84661
+ renewsAt: result.renewsAt ?? null
84662
+ };
84663
+ }
84664
+ return {
84665
+ status: result.reason === "revoked" ? "revoked" : result.reason === "refunded" ? "refunded" : result.reason === "expired" ? "expired" : "missing",
84666
+ key,
84667
+ email: result.email
84668
+ };
84669
+ }
84670
+ const last = persisted?.lastValidatedAt ?? 0;
84671
+ if (Date.now() - last < GRACE_PERIOD_MS) {
84672
+ return {
84673
+ status: "active",
84674
+ key,
84675
+ email: persisted?.email,
84676
+ lastValidatedAt: last,
84677
+ plan: persisted?.plan,
84678
+ renewsAt: persisted?.renewsAt ?? null
84679
+ };
84680
+ }
84681
+ return {
84682
+ status: "offline_grace_expired",
84683
+ key,
84684
+ email: persisted?.email,
84685
+ lastValidatedAt: last
84686
+ };
84687
+ }
84688
+ var timer = null;
84689
+ function startBackgroundValidation(onChange) {
84690
+ if (timer)
84691
+ clearInterval(timer);
84692
+ timer = setInterval(async () => {
84693
+ const state = await checkLicense();
84694
+ onChange(state);
84695
+ }, VALIDATE_INTERVAL_MS);
84696
+ }
84697
+ function stopBackgroundValidation() {
84698
+ if (timer) {
84699
+ clearInterval(timer);
84700
+ timer = null;
84701
+ }
84702
+ }
84703
+ }
84704
+ });
84705
+
84706
+ // ../desktop/dist/main/lib/receptionist.js
84707
+ var require_receptionist = __commonJS({
84708
+ "../desktop/dist/main/lib/receptionist.js"(exports2) {
84709
+ "use strict";
84710
+ Object.defineProperty(exports2, "__esModule", { value: true });
84711
+ exports2.RECEPTIONIST_PRICE_NOTE = void 0;
84712
+ exports2.buildConfig = buildConfig;
84713
+ exports2.createTrial = createTrial;
84714
+ exports2.listReceptionists = listReceptionists;
84715
+ exports2.listAll = listAll;
84716
+ exports2.activateReceptionist = activateReceptionist;
84717
+ exports2.forwardingSheet = forwardingSheet;
84718
+ exports2.removeReceptionist = removeReceptionist;
84719
+ var db_1 = require_db();
84720
+ var agency_1 = require_agency();
84721
+ var builder_1 = require_builder();
84722
+ var llm_1 = require_llm();
84723
+ var models_1 = require_models();
84724
+ function licenseKey() {
84725
+ try {
84726
+ const key = require_license().getActiveLicenseKey();
84727
+ if (key)
84728
+ return key;
84729
+ } catch {
84730
+ }
84731
+ const row = (0, db_1.getDb)().get("SELECT value FROM settings WHERE key = 'license_key'", []);
84732
+ return row?.value ?? null;
84733
+ }
84734
+ function apiBase2() {
84735
+ return process.env.SPROUTKIT_API_BASE_URL ?? "https://sproutkit.app";
84736
+ }
84737
+ async function api(method, path4, body) {
84738
+ const key = licenseKey();
84739
+ if (!key)
84740
+ throw new Error("Sign in with your Sproutkit licence first.");
84741
+ const r = await fetch(`${apiBase2()}${path4}`, {
84742
+ method,
84743
+ headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
84744
+ body: body ? JSON.stringify(body) : void 0,
84745
+ signal: AbortSignal.timeout(45e3)
84746
+ });
84747
+ const j = await r.json().catch(() => ({}));
84748
+ if (!r.ok)
84749
+ throw new Error(j.error ?? `sproutkit.app answered ${r.status}`);
84750
+ return j;
84751
+ }
84752
+ function countryOf(phone, address) {
84753
+ const p = (phone ?? "").replace(/[^\d+]/g, "");
84754
+ if (/^(\+44|0)/.test(p) || /\b[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}\b/i.test(address ?? ""))
84755
+ return "GB";
84756
+ if (/^(\+1)/.test(p) || /\b[A-Z]{2} \d{5}\b/.test(address ?? ""))
84757
+ return "US";
84758
+ return (0, agency_1.getAgencyProfile)().currency === "USD" ? "US" : "GB";
84759
+ }
84760
+ async function buildConfig(leadId) {
84761
+ const lead = (0, db_1.getDb)().get("SELECT name, type, city, address, phone, email, owner_email, site_url, admin_url, booking_url FROM leads WHERE id = ?", [leadId]);
84762
+ if (!lead)
84763
+ throw new Error("Lead not found");
84764
+ const config = {
84765
+ businessName: lead.name,
84766
+ businessType: lead.type || "local business",
84767
+ city: lead.city?.split(",")[0] || void 0,
84768
+ address: lead.address || void 0,
84769
+ phone: lead.phone || void 0,
84770
+ // No web addresses: a receptionist reading out a URL is useless on the
84771
+ // phone, and before handover the site is still a demo address.
84772
+ booking: lead.booking_url ? "Customers can book online through the booking page on the business's website." : void 0,
84773
+ country: countryOf(lead.phone, lead.address)
84774
+ };
84775
+ if (lead.site_url) {
84776
+ try {
84777
+ const r = await fetch(`${lead.site_url.replace(/\/$/, "")}/api/content`, { signal: AbortSignal.timeout(8e3) });
84778
+ if (r.ok) {
84779
+ const c2 = await r.json();
84780
+ if (c2.about)
84781
+ config.about = c2.about.slice(0, 1500);
84782
+ if (c2.services?.length) {
84783
+ config.services = c2.services.filter((s) => s.title).slice(0, 15).map((s) => ({ title: s.title, description: s.description, price: s.price }));
84784
+ }
84785
+ if (c2.contact?.hours?.length)
84786
+ config.hours = c2.contact.hours;
84787
+ if (!config.address && c2.contact?.address)
84788
+ config.address = c2.contact.address;
84789
+ if (!config.about && c2.business?.tagline)
84790
+ config.about = c2.business.tagline;
84791
+ }
84792
+ } catch {
84793
+ }
84794
+ }
84795
+ config.knowledge = await factSheet(leadId, lead.name, config) ?? void 0;
84796
+ return { config, ownerEmail: lead.owner_email || lead.email || null };
84797
+ }
84798
+ async function factSheet(leadId, name, known) {
84799
+ const source = (0, builder_1.siteSourceFor)(leadId);
84800
+ if (!source)
84801
+ return null;
84802
+ const anthropicKey = (() => {
84803
+ const row = (0, db_1.getDb)().get("SELECT value FROM settings WHERE key = 'anthropic_key'", []);
84804
+ if (!row)
84805
+ return null;
84806
+ try {
84807
+ return require_runtime().decryptSettingValue("anthropic_key", row.value);
84808
+ } catch {
84809
+ return null;
84810
+ }
84811
+ })();
84812
+ try {
84813
+ const res = await (0, llm_1.generateText)({
84814
+ apiKey: anthropicKey,
84815
+ model: (0, models_1.getPreferredModel)(),
84816
+ maxTokens: 4e3,
84817
+ timeoutMs: 18e4,
84818
+ prompt: `Below is the code of ${name}'s website. Write the fact sheet their phone receptionist will answer from.
84819
+
84820
+ Include every fact a caller could ask about, exactly as the site states it:
84821
+ - Menu / price list / services: every item with its price and any description (group by section if the site does).
84822
+ - Opening hours, address, area served, parking, accessibility.
84823
+ - How to book or order (walk-in, call, online), deposits, cancellation and other policies.
84824
+ - Anything else useful: specialities, dietary options, delivery, gift vouchers, FAQs, team.
84825
+
84826
+ Rules: plain text with short headings, no markdown tables, no code. Only facts present in the code \u2014 never invent or estimate. Skip web addresses, marketing slogans, reviews and styling. If the site has none of a section, leave that section out.
84827
+ ${known.services?.length || known.hours?.length ? `
84828
+ The owner has since edited these in their site editor \u2014 these win over the code:
84829
+ ${JSON.stringify({ services: known.services, hours: known.hours, about: known.about })}
84830
+ ` : ""}
84831
+ Website code:
84832
+ ${source.slice(0, 6e4)}`
84833
+ });
84834
+ const text = (res.text ?? "").trim();
84835
+ return text.length > 40 ? text.slice(0, 12e3) : null;
84836
+ } catch {
84837
+ return null;
84838
+ }
84839
+ }
84840
+ async function createTrial(leadId, extra) {
84841
+ const { config, ownerEmail } = await buildConfig(leadId);
84842
+ if (extra)
84843
+ config.extra = extra;
84844
+ const r = await api("POST", "/api/receptionists", {
84845
+ leadRef: String(leadId),
84846
+ config,
84847
+ ownerEmail,
84848
+ prospectPhones: config.phone ? [config.phone] : []
84849
+ });
84850
+ (0, db_1.getDb)().run("UPDATE leads SET receptionist_id = ?, receptionist_url = ?, receptionist_line = ?, receptionist_price_pence = COALESCE(receptionist_price_pence, 4900) WHERE id = ?", [r.receptionist.id, r.trialUrl, r.demoNumber, leadId]);
84851
+ return { trialUrl: r.trialUrl, id: r.receptionist.id, demoNumber: r.demoNumber };
84852
+ }
84853
+ async function listReceptionists() {
84854
+ return (await listAll()).receptionists;
84855
+ }
84856
+ async function listAll() {
84857
+ return api("GET", "/api/receptionists");
84858
+ }
84859
+ async function activateReceptionist(id, country, ownerEmail, ownerMobile, emergencies) {
84860
+ return api("POST", `/api/receptionists/${id}/activate`, { country, ownerEmail, ownerMobile, emergencies });
84861
+ }
84862
+ function forwardingSheet(receptionistNumber, country) {
84863
+ const n = receptionistNumber.replace(/[^\d+]/g, "");
84864
+ const lines = [
84865
+ `Send missed calls to your AI receptionist`,
84866
+ ``,
84867
+ `On your business mobile, dial each code and press call. Your phone still rings first \u2014 the receptionist only picks up when you can't.`,
84868
+ ``,
84869
+ `1. When you don't answer: **61*${n}#`,
84870
+ `2. When you're busy: **67*${n}#`,
84871
+ `3. When you have no signal: **62*${n}#`,
84872
+ ``,
84873
+ `Check each one is on: *#61# *#67# *#62#`,
84874
+ `To turn it all off again: ##004#`
84875
+ ];
84876
+ if (country === "US") {
84877
+ lines.push(``, `On Verizon instead: dial *71${n.replace(/^\+1/, "")} (no answer and busy). To turn it off: *73.`);
84878
+ }
84879
+ return lines.join("\n");
84880
+ }
84881
+ async function removeReceptionist(id, leadId) {
84882
+ await api("DELETE", `/api/receptionists/${id}`);
84883
+ (0, db_1.getDb)().run("UPDATE leads SET receptionist_id = NULL, receptionist_url = NULL WHERE id = ?", [leadId]);
84884
+ }
84885
+ exports2.RECEPTIONIST_PRICE_NOTE = "$49/month per live receptionist, up to 300 minutes";
84486
84886
  }
84487
84887
  });
84488
84888
 
@@ -97310,6 +97710,7 @@ var require_chat_tools = __commonJS({
97310
97710
  var lead_search_1 = require_lead_search();
97311
97711
  var ai_search_1 = require_ai_search();
97312
97712
  var pitch_text_1 = require_pitch_text();
97713
+ var receptionist_1 = require_receptionist();
97313
97714
  var places_1 = require_places();
97314
97715
  var booking_detect_1 = require_booking_detect();
97315
97716
  var site_audit_1 = require_site_audit();
@@ -98007,12 +98408,12 @@ If you'd rather not hear from me again, just reply and let me know.`;
98007
98408
  }
98008
98409
  case "prepare_outreach": {
98009
98410
  const explicit = input.ids;
98010
- const rows = explicit && explicit.length > 0 ? db2.all(`SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
98411
+ const rows = explicit && explicit.length > 0 ? db2.all(`SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url, receptionist_url, receptionist_line FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
98011
98412
  // Eligible = we have something to show them: either a demo site
98012
98413
  // we built (site_url) OR a booking page we built (booking_url).
98013
98414
  // Booking leads never get a site_url, so requiring it silently
98014
98415
  // excluded every one-shot booking customer.
98015
- "SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url FROM leads WHERE status IN ('site_built','discovered') AND ((site_url IS NOT NULL AND site_url != '') OR (booking_url IS NOT NULL AND booking_url != '')) ORDER BY score DESC NULLS LAST, id DESC",
98416
+ "SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url, receptionist_url, receptionist_line FROM leads WHERE status IN ('site_built','discovered') AND ((site_url IS NOT NULL AND site_url != '') OR (booking_url IS NOT NULL AND booking_url != '')) ORDER BY score DESC NULLS LAST, id DESC",
98016
98417
  []
98017
98418
  );
98018
98419
  const eligible = rows.filter((r) => r.site_url || r.booking_url);
@@ -98043,7 +98444,19 @@ If you'd rather not hear from me again, just reply and let me know.`;
98043
98444
  videoUrl = null;
98044
98445
  }
98045
98446
  }
98046
- const message = (0, pitch_text_1.withVideo)((isBookingLead ? bookingTemplate : template).replace(/\{name\}/g, r.name).replace(/\{site_url\}/g, (isBookingLead ? r.booking_url : r.site_url) ?? r.site_url ?? "").replace(/\{booking_how\}/g, r.booking_platform || "WhatsApp/phone"), isBookingLead ? null : videoUrl);
98447
+ let receptionistUrl = r.receptionist_url ?? null;
98448
+ let receptionistLine = r.receptionist_line ?? null;
98449
+ if (!isBookingLead && !receptionistUrl && r.site_url && getDecryptedSetting("receptionist_auto") === "1") {
98450
+ ctx.emitProgress?.({ channel: "outreach:progress", payload: { msg: `Setting up ${r.name}'s AI receptionist\u2026` } });
98451
+ try {
98452
+ const t = await (0, receptionist_1.createTrial)(r.id);
98453
+ receptionistUrl = t.trialUrl;
98454
+ receptionistLine = t.demoNumber;
98455
+ } catch {
98456
+ receptionistUrl = null;
98457
+ }
98458
+ }
98459
+ const message = (0, pitch_text_1.withVideo)((0, pitch_text_1.withReceptionist)((isBookingLead ? bookingTemplate : template).replace(/\{name\}/g, r.name).replace(/\{site_url\}/g, (isBookingLead ? r.booking_url : r.site_url) ?? r.site_url ?? "").replace(/\{booking_how\}/g, r.booking_platform || "WhatsApp/phone"), isBookingLead ? null : receptionistUrl, isBookingLead ? null : receptionistLine, r.name), isBookingLead ? null : videoUrl);
98047
98460
  const e164 = toE164(r.phone ?? "");
98048
98461
  if (!r.email && smtp && (preferEmail || !e164)) {
98049
98462
  ctx.emitProgress?.({
@@ -100054,7 +100467,7 @@ var require_package2 = __commonJS({
100054
100467
  "package.json"(exports2, module2) {
100055
100468
  module2.exports = {
100056
100469
  name: "sproutkit-code",
100057
- version: "1.13.0",
100470
+ version: "1.15.0",
100058
100471
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
100059
100472
  bin: {
100060
100473
  sproutkit: "./dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Sproutkit in your terminal — find leads, build sites, run outreach, all via chat.",
5
5
  "bin": {
6
6
  "sproutkit": "./dist/index.js"