sproutkit-code 1.12.0 → 1.14.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.
- package/dist/index.js +437 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -97,7 +97,8 @@ var require_runtime = __commonJS({
|
|
|
97
97
|
"report_key",
|
|
98
98
|
"smtp_pass",
|
|
99
99
|
"imap_pass",
|
|
100
|
-
"stripe_secret_key"
|
|
100
|
+
"stripe_secret_key",
|
|
101
|
+
"elevenlabs_key"
|
|
101
102
|
]);
|
|
102
103
|
function isSecretSetting(key) {
|
|
103
104
|
return SECRET_SETTINGS.has(key);
|
|
@@ -394,6 +395,36 @@ var require_db = __commonJS({
|
|
|
394
395
|
handed_over_at TEXT
|
|
395
396
|
);
|
|
396
397
|
`);
|
|
398
|
+
for (const col of [
|
|
399
|
+
"domain_choice TEXT",
|
|
400
|
+
"domain_price_pence INTEGER",
|
|
401
|
+
"domain_cost_usd REAL",
|
|
402
|
+
"domain_status TEXT",
|
|
403
|
+
"domain_order_id TEXT"
|
|
404
|
+
]) {
|
|
405
|
+
try {
|
|
406
|
+
db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
|
|
407
|
+
} catch {
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const col of ["receptionist_id TEXT", "receptionist_url TEXT", "receptionist_price_pence INTEGER"]) {
|
|
411
|
+
try {
|
|
412
|
+
db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
for (const col of ["pitch_video_url TEXT", "pitch_poster_url TEXT", "pitch_script TEXT"]) {
|
|
417
|
+
try {
|
|
418
|
+
db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const col of ["domain TEXT", "domain_pence INTEGER NOT NULL DEFAULT 0", "receptionist_pence INTEGER NOT NULL DEFAULT 0"]) {
|
|
423
|
+
try {
|
|
424
|
+
db2.exec(`ALTER TABLE deals ADD COLUMN ${col}`);
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
397
428
|
}
|
|
398
429
|
function getDb2() {
|
|
399
430
|
return db2;
|
|
@@ -81595,6 +81626,7 @@ var require_builder = __commonJS({
|
|
|
81595
81626
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
81596
81627
|
};
|
|
81597
81628
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
81629
|
+
exports2.siteSourceFor = siteSourceFor;
|
|
81598
81630
|
exports2.buildAndDeploySite = buildAndDeploySite;
|
|
81599
81631
|
exports2.redeployCmsOnly = redeployCmsOnly;
|
|
81600
81632
|
exports2.deployBookingOnly = deployBookingOnly2;
|
|
@@ -81810,6 +81842,9 @@ var require_builder = __commonJS({
|
|
|
81810
81842
|
} catch {
|
|
81811
81843
|
}
|
|
81812
81844
|
}
|
|
81845
|
+
function siteSourceFor(leadId) {
|
|
81846
|
+
return loadSnapshot(leadId)?.find((f) => f.file === "src/App.jsx")?.data ?? null;
|
|
81847
|
+
}
|
|
81813
81848
|
function loadSnapshot(leadId) {
|
|
81814
81849
|
const dir = snapshotDir(leadId);
|
|
81815
81850
|
if (!fs4.existsSync(dir))
|
|
@@ -84437,6 +84472,383 @@ Reply with only this, no other text:
|
|
|
84437
84472
|
}
|
|
84438
84473
|
});
|
|
84439
84474
|
|
|
84475
|
+
// ../desktop/dist/main/lib/pitch-text.js
|
|
84476
|
+
var require_pitch_text = __commonJS({
|
|
84477
|
+
"../desktop/dist/main/lib/pitch-text.js"(exports2) {
|
|
84478
|
+
"use strict";
|
|
84479
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
84480
|
+
exports2.withVideo = withVideo;
|
|
84481
|
+
exports2.withReceptionist = withReceptionist;
|
|
84482
|
+
function withVideo(message, videoUrl) {
|
|
84483
|
+
if (!videoUrl)
|
|
84484
|
+
return message.replace(/\n?[^\n]*\{video\}[^\n]*/g, "");
|
|
84485
|
+
if (message.includes("{video}"))
|
|
84486
|
+
return message.replace(/\{video\}/g, videoUrl);
|
|
84487
|
+
const line = `I also recorded a short video walking you through it: ${videoUrl}`;
|
|
84488
|
+
const lines = message.split("\n");
|
|
84489
|
+
const at = lines.findIndex((l) => /https?:\/\//.test(l));
|
|
84490
|
+
if (at < 0)
|
|
84491
|
+
return `${message}
|
|
84492
|
+
|
|
84493
|
+
${line}`;
|
|
84494
|
+
lines.splice(at + 1, 0, "", line);
|
|
84495
|
+
return lines.join("\n");
|
|
84496
|
+
}
|
|
84497
|
+
function withReceptionist(message, url) {
|
|
84498
|
+
if (!url)
|
|
84499
|
+
return message.replace(/\n?[^\n]*\{receptionist\}[^\n]*/g, "");
|
|
84500
|
+
if (message.includes("{receptionist}"))
|
|
84501
|
+
return message.replace(/\{receptionist\}/g, url);
|
|
84502
|
+
const line = `I've also set up an AI receptionist that would answer your calls 24/7. Give it a call: ${url}`;
|
|
84503
|
+
const lines = message.split("\n");
|
|
84504
|
+
const at = lines.findIndex((l) => /https?:\/\//.test(l));
|
|
84505
|
+
if (at < 0)
|
|
84506
|
+
return `${message}
|
|
84507
|
+
|
|
84508
|
+
${line}`;
|
|
84509
|
+
lines.splice(at + 1, 0, "", line);
|
|
84510
|
+
return lines.join("\n");
|
|
84511
|
+
}
|
|
84512
|
+
}
|
|
84513
|
+
});
|
|
84514
|
+
|
|
84515
|
+
// ../desktop/dist/main/lib/license.js
|
|
84516
|
+
var require_license = __commonJS({
|
|
84517
|
+
"../desktop/dist/main/lib/license.js"(exports2) {
|
|
84518
|
+
"use strict";
|
|
84519
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
84520
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
84521
|
+
};
|
|
84522
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
84523
|
+
exports2.clearLicense = clearLicense;
|
|
84524
|
+
exports2.getActiveLicenseKey = getActiveLicenseKey;
|
|
84525
|
+
exports2.activate = activate2;
|
|
84526
|
+
exports2.checkLicense = checkLicense;
|
|
84527
|
+
exports2.startBackgroundValidation = startBackgroundValidation;
|
|
84528
|
+
exports2.stopBackgroundValidation = stopBackgroundValidation;
|
|
84529
|
+
var electron_1 = require_electron();
|
|
84530
|
+
var crypto_1 = require("crypto");
|
|
84531
|
+
var fs_1 = __importDefault(require("fs"));
|
|
84532
|
+
var os_1 = __importDefault(require("os"));
|
|
84533
|
+
var path_1 = __importDefault(require("path"));
|
|
84534
|
+
var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
84535
|
+
var VALIDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
84536
|
+
function statePath() {
|
|
84537
|
+
return path_1.default.join(electron_1.app.getPath("userData"), "license.json");
|
|
84538
|
+
}
|
|
84539
|
+
function loadPersisted() {
|
|
84540
|
+
try {
|
|
84541
|
+
const raw = fs_1.default.readFileSync(statePath(), "utf-8");
|
|
84542
|
+
return JSON.parse(raw);
|
|
84543
|
+
} catch {
|
|
84544
|
+
return null;
|
|
84545
|
+
}
|
|
84546
|
+
}
|
|
84547
|
+
function savePersisted(data) {
|
|
84548
|
+
fs_1.default.writeFileSync(statePath(), JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
84549
|
+
}
|
|
84550
|
+
function clearLicense() {
|
|
84551
|
+
try {
|
|
84552
|
+
fs_1.default.unlinkSync(statePath());
|
|
84553
|
+
} catch {
|
|
84554
|
+
}
|
|
84555
|
+
}
|
|
84556
|
+
function getDeviceId() {
|
|
84557
|
+
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");
|
|
84558
|
+
const seed = `${ifs[0] ?? "unknown"}|${os_1.default.hostname()}`;
|
|
84559
|
+
return (0, crypto_1.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
|
|
84560
|
+
}
|
|
84561
|
+
function getStoredKey() {
|
|
84562
|
+
const p = loadPersisted();
|
|
84563
|
+
if (!p)
|
|
84564
|
+
return null;
|
|
84565
|
+
try {
|
|
84566
|
+
return electron_1.safeStorage.decryptString(Buffer.from(p.encryptedKey, "base64"));
|
|
84567
|
+
} catch {
|
|
84568
|
+
return null;
|
|
84569
|
+
}
|
|
84570
|
+
}
|
|
84571
|
+
function getActiveLicenseKey() {
|
|
84572
|
+
return getStoredKey();
|
|
84573
|
+
}
|
|
84574
|
+
function getApiBaseUrl() {
|
|
84575
|
+
return process.env.SPROUTKIT_API_BASE_URL ?? "https://sproutkit.app";
|
|
84576
|
+
}
|
|
84577
|
+
async function callValidateApi(key) {
|
|
84578
|
+
try {
|
|
84579
|
+
const r = await fetch(`${getApiBaseUrl()}/api/licenses/validate`, {
|
|
84580
|
+
method: "POST",
|
|
84581
|
+
headers: { "Content-Type": "application/json" },
|
|
84582
|
+
body: JSON.stringify({ key, deviceId: getDeviceId() }),
|
|
84583
|
+
signal: AbortSignal.timeout(8e3)
|
|
84584
|
+
});
|
|
84585
|
+
if (!r.ok)
|
|
84586
|
+
return null;
|
|
84587
|
+
const parsed = await r.json();
|
|
84588
|
+
if (!parsed.valid && parsed.reason === "unavailable")
|
|
84589
|
+
return null;
|
|
84590
|
+
return parsed;
|
|
84591
|
+
} catch {
|
|
84592
|
+
return null;
|
|
84593
|
+
}
|
|
84594
|
+
}
|
|
84595
|
+
async function activate2(key) {
|
|
84596
|
+
const result = await callValidateApi(key.trim());
|
|
84597
|
+
if (!result) {
|
|
84598
|
+
return { status: "missing", key };
|
|
84599
|
+
}
|
|
84600
|
+
if (!result.valid) {
|
|
84601
|
+
return {
|
|
84602
|
+
status: result.reason === "revoked" ? "revoked" : result.reason === "refunded" ? "refunded" : result.reason === "expired" ? "expired" : "missing",
|
|
84603
|
+
key,
|
|
84604
|
+
email: result.email
|
|
84605
|
+
};
|
|
84606
|
+
}
|
|
84607
|
+
const encrypted = electron_1.safeStorage.encryptString(key.trim()).toString("base64");
|
|
84608
|
+
savePersisted({
|
|
84609
|
+
encryptedKey: encrypted,
|
|
84610
|
+
email: result.email,
|
|
84611
|
+
lastValidatedAt: Date.now(),
|
|
84612
|
+
plan: result.plan,
|
|
84613
|
+
renewsAt: result.renewsAt ?? null
|
|
84614
|
+
});
|
|
84615
|
+
return {
|
|
84616
|
+
status: "active",
|
|
84617
|
+
key,
|
|
84618
|
+
email: result.email,
|
|
84619
|
+
lastValidatedAt: Date.now(),
|
|
84620
|
+
plan: result.plan,
|
|
84621
|
+
renewsAt: result.renewsAt ?? null
|
|
84622
|
+
};
|
|
84623
|
+
}
|
|
84624
|
+
async function checkLicense() {
|
|
84625
|
+
const key = getStoredKey();
|
|
84626
|
+
if (!key)
|
|
84627
|
+
return { status: "missing" };
|
|
84628
|
+
const persisted = loadPersisted();
|
|
84629
|
+
const result = await callValidateApi(key);
|
|
84630
|
+
if (result) {
|
|
84631
|
+
if (result.valid) {
|
|
84632
|
+
savePersisted({
|
|
84633
|
+
encryptedKey: persisted.encryptedKey,
|
|
84634
|
+
email: result.email,
|
|
84635
|
+
lastValidatedAt: Date.now(),
|
|
84636
|
+
plan: result.plan,
|
|
84637
|
+
renewsAt: result.renewsAt ?? null
|
|
84638
|
+
});
|
|
84639
|
+
return {
|
|
84640
|
+
status: "active",
|
|
84641
|
+
key,
|
|
84642
|
+
email: result.email,
|
|
84643
|
+
lastValidatedAt: Date.now(),
|
|
84644
|
+
plan: result.plan,
|
|
84645
|
+
renewsAt: result.renewsAt ?? null
|
|
84646
|
+
};
|
|
84647
|
+
}
|
|
84648
|
+
return {
|
|
84649
|
+
status: result.reason === "revoked" ? "revoked" : result.reason === "refunded" ? "refunded" : result.reason === "expired" ? "expired" : "missing",
|
|
84650
|
+
key,
|
|
84651
|
+
email: result.email
|
|
84652
|
+
};
|
|
84653
|
+
}
|
|
84654
|
+
const last = persisted?.lastValidatedAt ?? 0;
|
|
84655
|
+
if (Date.now() - last < GRACE_PERIOD_MS) {
|
|
84656
|
+
return {
|
|
84657
|
+
status: "active",
|
|
84658
|
+
key,
|
|
84659
|
+
email: persisted?.email,
|
|
84660
|
+
lastValidatedAt: last,
|
|
84661
|
+
plan: persisted?.plan,
|
|
84662
|
+
renewsAt: persisted?.renewsAt ?? null
|
|
84663
|
+
};
|
|
84664
|
+
}
|
|
84665
|
+
return {
|
|
84666
|
+
status: "offline_grace_expired",
|
|
84667
|
+
key,
|
|
84668
|
+
email: persisted?.email,
|
|
84669
|
+
lastValidatedAt: last
|
|
84670
|
+
};
|
|
84671
|
+
}
|
|
84672
|
+
var timer = null;
|
|
84673
|
+
function startBackgroundValidation(onChange) {
|
|
84674
|
+
if (timer)
|
|
84675
|
+
clearInterval(timer);
|
|
84676
|
+
timer = setInterval(async () => {
|
|
84677
|
+
const state = await checkLicense();
|
|
84678
|
+
onChange(state);
|
|
84679
|
+
}, VALIDATE_INTERVAL_MS);
|
|
84680
|
+
}
|
|
84681
|
+
function stopBackgroundValidation() {
|
|
84682
|
+
if (timer) {
|
|
84683
|
+
clearInterval(timer);
|
|
84684
|
+
timer = null;
|
|
84685
|
+
}
|
|
84686
|
+
}
|
|
84687
|
+
}
|
|
84688
|
+
});
|
|
84689
|
+
|
|
84690
|
+
// ../desktop/dist/main/lib/receptionist.js
|
|
84691
|
+
var require_receptionist = __commonJS({
|
|
84692
|
+
"../desktop/dist/main/lib/receptionist.js"(exports2) {
|
|
84693
|
+
"use strict";
|
|
84694
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
84695
|
+
exports2.RECEPTIONIST_PRICE_NOTE = void 0;
|
|
84696
|
+
exports2.buildConfig = buildConfig;
|
|
84697
|
+
exports2.createTrial = createTrial;
|
|
84698
|
+
exports2.listReceptionists = listReceptionists;
|
|
84699
|
+
exports2.activateReceptionist = activateReceptionist;
|
|
84700
|
+
exports2.removeReceptionist = removeReceptionist;
|
|
84701
|
+
var db_1 = require_db();
|
|
84702
|
+
var agency_1 = require_agency();
|
|
84703
|
+
var builder_1 = require_builder();
|
|
84704
|
+
var llm_1 = require_llm();
|
|
84705
|
+
var models_1 = require_models();
|
|
84706
|
+
function licenseKey() {
|
|
84707
|
+
try {
|
|
84708
|
+
const key = require_license().getActiveLicenseKey();
|
|
84709
|
+
if (key)
|
|
84710
|
+
return key;
|
|
84711
|
+
} catch {
|
|
84712
|
+
}
|
|
84713
|
+
const row = (0, db_1.getDb)().get("SELECT value FROM settings WHERE key = 'license_key'", []);
|
|
84714
|
+
return row?.value ?? null;
|
|
84715
|
+
}
|
|
84716
|
+
function apiBase2() {
|
|
84717
|
+
return process.env.SPROUTKIT_API_BASE_URL ?? "https://sproutkit.app";
|
|
84718
|
+
}
|
|
84719
|
+
async function api(method, path4, body) {
|
|
84720
|
+
const key = licenseKey();
|
|
84721
|
+
if (!key)
|
|
84722
|
+
throw new Error("Sign in with your Sproutkit licence first.");
|
|
84723
|
+
const r = await fetch(`${apiBase2()}${path4}`, {
|
|
84724
|
+
method,
|
|
84725
|
+
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
84726
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
84727
|
+
signal: AbortSignal.timeout(45e3)
|
|
84728
|
+
});
|
|
84729
|
+
const j = await r.json().catch(() => ({}));
|
|
84730
|
+
if (!r.ok)
|
|
84731
|
+
throw new Error(j.error ?? `sproutkit.app answered ${r.status}`);
|
|
84732
|
+
return j;
|
|
84733
|
+
}
|
|
84734
|
+
function countryOf(phone, address) {
|
|
84735
|
+
const p = (phone ?? "").replace(/[^\d+]/g, "");
|
|
84736
|
+
if (/^(\+44|0)/.test(p) || /\b[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}\b/i.test(address ?? ""))
|
|
84737
|
+
return "GB";
|
|
84738
|
+
if (/^(\+1)/.test(p) || /\b[A-Z]{2} \d{5}\b/.test(address ?? ""))
|
|
84739
|
+
return "US";
|
|
84740
|
+
return (0, agency_1.getAgencyProfile)().currency === "USD" ? "US" : "GB";
|
|
84741
|
+
}
|
|
84742
|
+
async function buildConfig(leadId) {
|
|
84743
|
+
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]);
|
|
84744
|
+
if (!lead)
|
|
84745
|
+
throw new Error("Lead not found");
|
|
84746
|
+
const config = {
|
|
84747
|
+
businessName: lead.name,
|
|
84748
|
+
businessType: lead.type || "local business",
|
|
84749
|
+
city: lead.city?.split(",")[0] || void 0,
|
|
84750
|
+
address: lead.address || void 0,
|
|
84751
|
+
phone: lead.phone || void 0,
|
|
84752
|
+
// No web addresses: a receptionist reading out a URL is useless on the
|
|
84753
|
+
// phone, and before handover the site is still a demo address.
|
|
84754
|
+
booking: lead.booking_url ? "Customers can book online through the booking page on the business's website." : void 0,
|
|
84755
|
+
country: countryOf(lead.phone, lead.address)
|
|
84756
|
+
};
|
|
84757
|
+
if (lead.site_url) {
|
|
84758
|
+
try {
|
|
84759
|
+
const r = await fetch(`${lead.site_url.replace(/\/$/, "")}/api/content`, { signal: AbortSignal.timeout(8e3) });
|
|
84760
|
+
if (r.ok) {
|
|
84761
|
+
const c2 = await r.json();
|
|
84762
|
+
if (c2.about)
|
|
84763
|
+
config.about = c2.about.slice(0, 1500);
|
|
84764
|
+
if (c2.services?.length) {
|
|
84765
|
+
config.services = c2.services.filter((s) => s.title).slice(0, 15).map((s) => ({ title: s.title, description: s.description, price: s.price }));
|
|
84766
|
+
}
|
|
84767
|
+
if (c2.contact?.hours?.length)
|
|
84768
|
+
config.hours = c2.contact.hours;
|
|
84769
|
+
if (!config.address && c2.contact?.address)
|
|
84770
|
+
config.address = c2.contact.address;
|
|
84771
|
+
if (!config.about && c2.business?.tagline)
|
|
84772
|
+
config.about = c2.business.tagline;
|
|
84773
|
+
}
|
|
84774
|
+
} catch {
|
|
84775
|
+
}
|
|
84776
|
+
}
|
|
84777
|
+
config.knowledge = await factSheet(leadId, lead.name, config) ?? void 0;
|
|
84778
|
+
return { config, ownerEmail: lead.owner_email || lead.email || null };
|
|
84779
|
+
}
|
|
84780
|
+
async function factSheet(leadId, name, known) {
|
|
84781
|
+
const source = (0, builder_1.siteSourceFor)(leadId);
|
|
84782
|
+
if (!source)
|
|
84783
|
+
return null;
|
|
84784
|
+
const anthropicKey = (() => {
|
|
84785
|
+
const row = (0, db_1.getDb)().get("SELECT value FROM settings WHERE key = 'anthropic_key'", []);
|
|
84786
|
+
if (!row)
|
|
84787
|
+
return null;
|
|
84788
|
+
try {
|
|
84789
|
+
return require_runtime().decryptSettingValue("anthropic_key", row.value);
|
|
84790
|
+
} catch {
|
|
84791
|
+
return null;
|
|
84792
|
+
}
|
|
84793
|
+
})();
|
|
84794
|
+
try {
|
|
84795
|
+
const res = await (0, llm_1.generateText)({
|
|
84796
|
+
apiKey: anthropicKey,
|
|
84797
|
+
model: (0, models_1.getPreferredModel)(),
|
|
84798
|
+
maxTokens: 4e3,
|
|
84799
|
+
timeoutMs: 18e4,
|
|
84800
|
+
prompt: `Below is the code of ${name}'s website. Write the fact sheet their phone receptionist will answer from.
|
|
84801
|
+
|
|
84802
|
+
Include every fact a caller could ask about, exactly as the site states it:
|
|
84803
|
+
- Menu / price list / services: every item with its price and any description (group by section if the site does).
|
|
84804
|
+
- Opening hours, address, area served, parking, accessibility.
|
|
84805
|
+
- How to book or order (walk-in, call, online), deposits, cancellation and other policies.
|
|
84806
|
+
- Anything else useful: specialities, dietary options, delivery, gift vouchers, FAQs, team.
|
|
84807
|
+
|
|
84808
|
+
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.
|
|
84809
|
+
${known.services?.length || known.hours?.length ? `
|
|
84810
|
+
The owner has since edited these in their site editor \u2014 these win over the code:
|
|
84811
|
+
${JSON.stringify({ services: known.services, hours: known.hours, about: known.about })}
|
|
84812
|
+
` : ""}
|
|
84813
|
+
Website code:
|
|
84814
|
+
${source.slice(0, 6e4)}`
|
|
84815
|
+
});
|
|
84816
|
+
const text = (res.text ?? "").trim();
|
|
84817
|
+
return text.length > 40 ? text.slice(0, 12e3) : null;
|
|
84818
|
+
} catch {
|
|
84819
|
+
return null;
|
|
84820
|
+
}
|
|
84821
|
+
}
|
|
84822
|
+
async function createTrial(leadId, extra) {
|
|
84823
|
+
const { config, ownerEmail } = await buildConfig(leadId);
|
|
84824
|
+
if (extra)
|
|
84825
|
+
config.extra = extra;
|
|
84826
|
+
const r = await api("POST", "/api/receptionists", {
|
|
84827
|
+
leadRef: String(leadId),
|
|
84828
|
+
config,
|
|
84829
|
+
ownerEmail
|
|
84830
|
+
});
|
|
84831
|
+
(0, db_1.getDb)().run("UPDATE leads SET receptionist_id = ?, receptionist_url = ?, receptionist_price_pence = COALESCE(receptionist_price_pence, 4900) WHERE id = ?", [
|
|
84832
|
+
r.receptionist.id,
|
|
84833
|
+
r.trialUrl,
|
|
84834
|
+
leadId
|
|
84835
|
+
]);
|
|
84836
|
+
return { trialUrl: r.trialUrl, id: r.receptionist.id };
|
|
84837
|
+
}
|
|
84838
|
+
async function listReceptionists() {
|
|
84839
|
+
return (await api("GET", "/api/receptionists")).receptionists;
|
|
84840
|
+
}
|
|
84841
|
+
async function activateReceptionist(id, country, ownerEmail) {
|
|
84842
|
+
return api("POST", `/api/receptionists/${id}/activate`, { country, ownerEmail });
|
|
84843
|
+
}
|
|
84844
|
+
async function removeReceptionist(id, leadId) {
|
|
84845
|
+
await api("DELETE", `/api/receptionists/${id}`);
|
|
84846
|
+
(0, db_1.getDb)().run("UPDATE leads SET receptionist_id = NULL, receptionist_url = NULL WHERE id = ?", [leadId]);
|
|
84847
|
+
}
|
|
84848
|
+
exports2.RECEPTIONIST_PRICE_NOTE = "$49/month per live receptionist, up to 300 minutes";
|
|
84849
|
+
}
|
|
84850
|
+
});
|
|
84851
|
+
|
|
84440
84852
|
// ../desktop/dist/main/lib/followup.js
|
|
84441
84853
|
var require_followup = __commonJS({
|
|
84442
84854
|
"../desktop/dist/main/lib/followup.js"(exports2) {
|
|
@@ -97260,6 +97672,8 @@ var require_chat_tools = __commonJS({
|
|
|
97260
97672
|
var builder_1 = require_builder();
|
|
97261
97673
|
var lead_search_1 = require_lead_search();
|
|
97262
97674
|
var ai_search_1 = require_ai_search();
|
|
97675
|
+
var pitch_text_1 = require_pitch_text();
|
|
97676
|
+
var receptionist_1 = require_receptionist();
|
|
97263
97677
|
var places_1 = require_places();
|
|
97264
97678
|
var booking_detect_1 = require_booking_detect();
|
|
97265
97679
|
var site_audit_1 = require_site_audit();
|
|
@@ -97957,12 +98371,12 @@ If you'd rather not hear from me again, just reply and let me know.`;
|
|
|
97957
98371
|
}
|
|
97958
98372
|
case "prepare_outreach": {
|
|
97959
98373
|
const explicit = input.ids;
|
|
97960
|
-
const rows = explicit && explicit.length > 0 ? db2.all(`SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
|
|
98374
|
+
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 FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
|
|
97961
98375
|
// Eligible = we have something to show them: either a demo site
|
|
97962
98376
|
// we built (site_url) OR a booking page we built (booking_url).
|
|
97963
98377
|
// Booking leads never get a site_url, so requiring it silently
|
|
97964
98378
|
// excluded every one-shot booking customer.
|
|
97965
|
-
"SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_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",
|
|
98379
|
+
"SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url, receptionist_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",
|
|
97966
98380
|
[]
|
|
97967
98381
|
);
|
|
97968
98382
|
const eligible = rows.filter((r) => r.site_url || r.booking_url);
|
|
@@ -97984,7 +98398,25 @@ If you'd rather not hear from me again, just reply and let me know.`;
|
|
|
97984
98398
|
let harvested = 0;
|
|
97985
98399
|
for (const r of eligible) {
|
|
97986
98400
|
const isBookingLead = r.lead_kind === "no_booking";
|
|
97987
|
-
|
|
98401
|
+
let videoUrl = r.pitch_video_url ?? null;
|
|
98402
|
+
if (!isBookingLead && !videoUrl && r.site_url && getDecryptedSetting("pitch_auto") === "1") {
|
|
98403
|
+
ctx.emitProgress?.({ channel: "outreach:progress", payload: { msg: `Recording a video for ${r.name}\u2026` } });
|
|
98404
|
+
try {
|
|
98405
|
+
videoUrl = (await ctx.invokeIpc("pitch:make", r.id)).url;
|
|
98406
|
+
} catch {
|
|
98407
|
+
videoUrl = null;
|
|
98408
|
+
}
|
|
98409
|
+
}
|
|
98410
|
+
let receptionistUrl = r.receptionist_url ?? null;
|
|
98411
|
+
if (!isBookingLead && !receptionistUrl && r.site_url && getDecryptedSetting("receptionist_auto") === "1") {
|
|
98412
|
+
ctx.emitProgress?.({ channel: "outreach:progress", payload: { msg: `Setting up ${r.name}'s AI receptionist\u2026` } });
|
|
98413
|
+
try {
|
|
98414
|
+
receptionistUrl = (await (0, receptionist_1.createTrial)(r.id)).trialUrl;
|
|
98415
|
+
} catch {
|
|
98416
|
+
receptionistUrl = null;
|
|
98417
|
+
}
|
|
98418
|
+
}
|
|
98419
|
+
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 : videoUrl);
|
|
97988
98420
|
const e164 = toE164(r.phone ?? "");
|
|
97989
98421
|
if (!r.email && smtp && (preferEmail || !e164)) {
|
|
97990
98422
|
ctx.emitProgress?.({
|
|
@@ -99995,7 +100427,7 @@ var require_package2 = __commonJS({
|
|
|
99995
100427
|
"package.json"(exports2, module2) {
|
|
99996
100428
|
module2.exports = {
|
|
99997
100429
|
name: "sproutkit-code",
|
|
99998
|
-
version: "1.
|
|
100430
|
+
version: "1.14.0",
|
|
99999
100431
|
description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
|
|
100000
100432
|
bin: {
|
|
100001
100433
|
sproutkit: "./dist/index.js"
|