sproutkit-code 1.15.0 → 1.16.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 +44 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -407,7 +407,7 @@ 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"]) {
410
+ for (const col of ["receptionist_id TEXT", "receptionist_url TEXT", "receptionist_price_pence INTEGER", "receptionist_line TEXT", "front_desk_chat INTEGER NOT NULL DEFAULT 0"]) {
411
411
  try {
412
412
  db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
413
413
  } catch {
@@ -31510,7 +31510,7 @@ var require_analytics_runtime = __commonJS({
31510
31510
  "../desktop/dist/main/lib/analytics-runtime.js"(exports2) {
31511
31511
  "use strict";
31512
31512
  Object.defineProperty(exports2, "__esModule", { value: true });
31513
- exports2.TRACK_SNIPPET = exports2.API_STATS_JS = exports2.API_TRACK_JS = void 0;
31513
+ exports2.FRONT_DESK_SNIPPET = exports2.TRACK_SNIPPET = exports2.API_STATS_JS = exports2.API_TRACK_JS = void 0;
31514
31514
  exports2.API_TRACK_JS = `import { list, put } from "@vercel/blob";
31515
31515
  import { createHash, createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
31516
31516
 
@@ -31876,6 +31876,7 @@ export default async function handler(req, res) {
31876
31876
  }, true);
31877
31877
  })();
31878
31878
  </script>`;
31879
+ exports2.FRONT_DESK_SNIPPET = `<script src="${process.env.SPROUTKIT_API_BASE_URL ?? "https://sproutkit.app"}/front-desk.js" defer></script>`;
31879
31880
  }
31880
31881
  });
31881
31882
 
@@ -81633,6 +81634,7 @@ var require_builder = __commonJS({
81633
81634
  exports2.deployStandaloneBooking = deployStandaloneBooking2;
81634
81635
  exports2.handoverSite = handoverSite2;
81635
81636
  exports2.addEditorToSite = addEditorToSite2;
81637
+ exports2.redeployAsIs = redeployAsIs;
81636
81638
  var sdk_1 = __importDefault(require_sdk());
81637
81639
  var axios_1 = __importDefault(require_axios());
81638
81640
  var crypto_1 = require("crypto");
@@ -82402,7 +82404,7 @@ ${prompt}` : prompt;
82402
82404
  onProgress("Deploying to Vercel...");
82403
82405
  const indexHtml = `<!DOCTYPE html>
82404
82406
  <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head>
82405
- <body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}</body></html>`;
82407
+ <body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}${analytics_runtime_1.FRONT_DESK_SNIPPET}</body></html>`;
82406
82408
  const mainJsx = `import { StrictMode } from 'react';
82407
82409
  import { createRoot } from 'react-dom/client';
82408
82410
  import App from './App.jsx';
@@ -82633,7 +82635,7 @@ export default defineConfig({ plugins: [react()] });`;
82633
82635
  { source: "/book/", destination: "/book/index.html" }
82634
82636
  ]
82635
82637
  });
82636
- const indexHtmlCms = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}</body></html>`;
82638
+ const indexHtmlCms = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}${analytics_runtime_1.FRONT_DESK_SNIPPET}</body></html>`;
82637
82639
  const mainJsxCms = `import { StrictMode } from 'react';
82638
82640
  import { createRoot } from 'react-dom/client';
82639
82641
  import App from './App.jsx';
@@ -82712,7 +82714,7 @@ export default defineConfig({ plugins: [react()] });`;
82712
82714
  snapshot[appJsxIndex] = { ...snapshot[appJsxIndex], data: modifiedApp };
82713
82715
  saveSnapshot(lead.id, snapshot);
82714
82716
  const files = snapshot.map((f) => ({ file: f.file, data: f.data, encoding: f.encoding }));
82715
- const indexHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}</body></html>`;
82717
+ const indexHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}${analytics_runtime_1.FRONT_DESK_SNIPPET}</body></html>`;
82716
82718
  const mainJsx = `import { StrictMode } from 'react';
82717
82719
  import { createRoot } from 'react-dom/client';
82718
82720
  import App from './App.jsx';
@@ -83015,7 +83017,7 @@ export default defineConfig({ plugins: [react()] });`;
83015
83017
  deps["@vercel/blob"] = "^0.23.4";
83016
83018
  deps.bcryptjs = "^2.4.3";
83017
83019
  }
83018
- const indexHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}</body></html>`;
83020
+ const indexHtml = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${lead.name}</title></head><body style="margin:0">${bannerFor(lead)}<div id="root"></div><script type="module" src="/src/main.jsx"></script>${analytics_runtime_1.TRACK_SNIPPET}${analytics_runtime_1.FRONT_DESK_SNIPPET}</body></html>`;
83019
83021
  files.push({ file: "index.html", data: indexHtml, encoding: "utf-8" }, {
83020
83022
  file: "src/main.jsx",
83021
83023
  data: `import { StrictMode } from 'react';
@@ -83206,6 +83208,22 @@ Your previous reply was unusable (${lastErr}). Follow the reply format exactly.`
83206
83208
  }
83207
83209
  throw new Error(`Couldn't connect this site to the editor (${lastErr}). Try again \u2014 nothing was changed.`);
83208
83210
  }
83211
+ async function redeployAsIs(args) {
83212
+ const { lead } = args;
83213
+ if (lead.booking_url) {
83214
+ throw new Error("This site has online booking, which a redeploy would remove. Skipped to keep bookings working.");
83215
+ }
83216
+ const uid = (0, db_1.getDb)().get("SELECT site_uid FROM leads WHERE id = ?", [lead.id])?.site_uid ?? null;
83217
+ return redeployWithoutBanner({
83218
+ lead,
83219
+ vercelToken: args.vercelToken,
83220
+ blobToken: args.blobToken,
83221
+ hasCms: !!lead.admin_url,
83222
+ adminPassword: lead.admin_password ?? null,
83223
+ siteUid: uid,
83224
+ onProgress: args.onProgress
83225
+ });
83226
+ }
83209
83227
  function hasBookLink(code) {
83210
83228
  return /(href\s*=\s*["'`{]*\s*["'`]?\/book\/?["'`}]?)|(location(?:\.href)?\s*=\s*["'`]\/book["'`])/i.test(code);
83211
83229
  }
@@ -84495,13 +84513,14 @@ ${line}`;
84495
84513
  lines.splice(at + 1, 0, "", line);
84496
84514
  return lines.join("\n");
84497
84515
  }
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)
84516
+ function withReceptionist(message, url, demoNumber, businessName, chatOn) {
84517
+ void demoNumber;
84518
+ message = message.replace(/\{front_desk\}/g, "{receptionist}");
84519
+ if (!url)
84501
84520
  return message.replace(/\n?[^\n]*\{receptionist\}[^\n]*/g, "");
84502
84521
  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}.`;
84522
+ return message.replace(/\{receptionist\}/g, url);
84523
+ const line = `I've also set up a virtual front desk for ${businessName ?? "you"}: it answers your calls when you can't${chatOn ? ", and questions on your website" : ""}, 24/7. You can try it here: ${url}`;
84505
84524
  const lines = message.split("\n");
84506
84525
  const at = lines.findIndex((l) => /https?:\/\//.test(l));
84507
84526
  if (at < 0)
@@ -84715,6 +84734,7 @@ var require_receptionist = __commonJS({
84715
84734
  exports2.listAll = listAll;
84716
84735
  exports2.activateReceptionist = activateReceptionist;
84717
84736
  exports2.forwardingSheet = forwardingSheet;
84737
+ exports2.setChat = setChat;
84718
84738
  exports2.removeReceptionist = removeReceptionist;
84719
84739
  var db_1 = require_db();
84720
84740
  var agency_1 = require_agency();
@@ -84841,11 +84861,14 @@ ${source.slice(0, 6e4)}`
84841
84861
  const { config, ownerEmail } = await buildConfig(leadId);
84842
84862
  if (extra)
84843
84863
  config.extra = extra;
84864
+ const site = (0, db_1.getDb)().get("SELECT site_url, domain_choice, domain_status FROM leads WHERE id = ?", [leadId]);
84865
+ const siteHosts = [site?.site_url, site?.domain_status === "bought" ? site.domain_choice : null].filter((h) => !!h);
84844
84866
  const r = await api("POST", "/api/receptionists", {
84845
84867
  leadRef: String(leadId),
84846
84868
  config,
84847
84869
  ownerEmail,
84848
- prospectPhones: config.phone ? [config.phone] : []
84870
+ prospectPhones: config.phone ? [config.phone] : [],
84871
+ siteHosts
84849
84872
  });
84850
84873
  (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
84874
  return { trialUrl: r.trialUrl, id: r.receptionist.id, demoNumber: r.demoNumber };
@@ -84878,6 +84901,10 @@ ${source.slice(0, 6e4)}`
84878
84901
  }
84879
84902
  return lines.join("\n");
84880
84903
  }
84904
+ async function setChat(id, leadId, on) {
84905
+ await api("PATCH", `/api/receptionists/${id}`, { chatEnabled: on });
84906
+ (0, db_1.getDb)().run("UPDATE leads SET front_desk_chat = ? WHERE id = ?", [on ? 1 : 0, leadId]);
84907
+ }
84881
84908
  async function removeReceptionist(id, leadId) {
84882
84909
  await api("DELETE", `/api/receptionists/${id}`);
84883
84910
  (0, db_1.getDb)().run("UPDATE leads SET receptionist_id = NULL, receptionist_url = NULL WHERE id = ?", [leadId]);
@@ -98408,12 +98435,12 @@ If you'd rather not hear from me again, just reply and let me know.`;
98408
98435
  }
98409
98436
  case "prepare_outreach": {
98410
98437
  const explicit = input.ids;
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(
98438
+ 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, front_desk_chat FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
98412
98439
  // Eligible = we have something to show them: either a demo site
98413
98440
  // we built (site_url) OR a booking page we built (booking_url).
98414
98441
  // Booking leads never get a site_url, so requiring it silently
98415
98442
  // excluded every one-shot booking customer.
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",
98443
+ "SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url, receptionist_url, receptionist_line, front_desk_chat 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",
98417
98444
  []
98418
98445
  );
98419
98446
  const eligible = rows.filter((r) => r.site_url || r.booking_url);
@@ -98447,7 +98474,7 @@ If you'd rather not hear from me again, just reply and let me know.`;
98447
98474
  let receptionistUrl = r.receptionist_url ?? null;
98448
98475
  let receptionistLine = r.receptionist_line ?? null;
98449
98476
  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` } });
98477
+ ctx.emitProgress?.({ channel: "outreach:progress", payload: { msg: `Setting up Front Desk for ${r.name}\u2026` } });
98451
98478
  try {
98452
98479
  const t = await (0, receptionist_1.createTrial)(r.id);
98453
98480
  receptionistUrl = t.trialUrl;
@@ -98456,7 +98483,7 @@ If you'd rather not hear from me again, just reply and let me know.`;
98456
98483
  receptionistUrl = null;
98457
98484
  }
98458
98485
  }
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);
98486
+ 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, !!r.front_desk_chat), isBookingLead ? null : videoUrl);
98460
98487
  const e164 = toE164(r.phone ?? "");
98461
98488
  if (!r.email && smtp && (preferEmail || !e164)) {
98462
98489
  ctx.emitProgress?.({
@@ -100467,7 +100494,7 @@ var require_package2 = __commonJS({
100467
100494
  "package.json"(exports2, module2) {
100468
100495
  module2.exports = {
100469
100496
  name: "sproutkit-code",
100470
- version: "1.15.0",
100497
+ version: "1.16.0",
100471
100498
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
100472
100499
  bin: {
100473
100500
  sproutkit: "./dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "1.15.0",
3
+ "version": "1.16.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"